Featured resource
pluralsight tech forecast
2025 Tech Forecast

Which technologies will dominate in 2025? And what skills do you need to keep up?

Check it out
Hamburger Icon
  • Labs icon Lab
  • Core Tech
Labs

Guided: Exploring Advanced Solidity Concepts - Part 1

In this hands-on lab, you'll build upon the knowledge gained from the Solidity introduction lab. You'll dive deeper into essential topics such as contract inheritance, interfaces, function modifiers, error handling, event logging, abstract contracts, and libraries. Through engaging hands-on exercises, you'll further enhance your skills in designing and developing cutting-edge decentralized applications. This lab represents the next level in creating robust and secure smart contracts, empowering you to take your blockchain development journey to new heights.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 1h 38m
Published
Clock icon Aug 21, 2023

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    Welcome to the "Guided: Exploring Advanced Solidity Concepts" lab!

    Expanding upon the concepts established in the "Guided: Introduction to Solidity Development using Hardhat" lab, this two-part exploration will take you further into the intricate world of Solidity by introducing advanced features.

    Using the Tickets.sol contract you'll find to the right, you'll discover how to implement Libraries, Events, Function Modifiers, Inheritance, and even the process of importing external files.

    This contract centers around managing tickets by mapping account addresses to their claim statuses. Through the constructor, it is deployed with a total number of available tickets.

    The heart of its logic lies in the claimTicket() function.

    This function first requires that the caller has not already claimed a ticket. If they have not, then add them to the ticketsClaimed mapping and decrement the number of available tickets. Here is one of the first areas that needs to be addressed. Considering that availableTickets is an unsigned integer, there is a potential underflow exception that could occur if there are 0 available tickets. You'll learn a couple ways of addressing that condition and how to share that logic across contracts.

    Considering potential improvements to this function, it could be helpful to inform others about this change using Solidity Events. You'll explore the benefits of emitting events from your contracts.

    In addition to this core functionality, there are two additional functions to help support this core feature.

    The ticketClaimed() function is used to verify if the caller has already claimed a ticket. While helpful specifically for the caller, you may also want to be able to verify if a different account has claimed a ticket.

    The addTickets() function enables the caller to increase the number of available tickets. For security purposes, it's sensible to restrict this function to the contract owner. In fact, the require condition that enforces this restriction could be useful in other parts of the contract as well. You'll investigate ways to share this logic across different functions within the same contract, and potentially across other contract files.

    When you are ready to begin exploring these concepts, click the Next step > button below.

  2. Challenge

    Sharing Functionality with a Library

    The first thing that needs to be addressed is the potential underflow exception when trying to subtract 1 when the availableTickets is already 0. Since availableTickets is an unsigned integer type, trying to set its value to -1 would cause an underflow exception. When exceptions occur in a solidity contract all state changes are reverted, all remaining gas in the transaction is consumed, and the exception propagates up the chain of calls. For this simple contract, that is not a concern, but when your contracts grow in complexity these will definitely become more of a concern.

    To demonstrate this error open the test/StepTwoTests.js unit test file and specifically look at the unit test "Unsigned Underflow Error". Quickly reviewing this test:

    • The Tickets contract is being deployed with only 1 ticket. Note that it is being deployed under the first address returned by the ethers.getSigners() method, stored in the owner variable.
    • The next address, stored in the first position of the addr array, claims that ticket.
    • The next address, addr[1], will attempt to also claim a ticket, but it should not be able to do so since there is only one available ticket. There are a couple of ways this panic code can be mitigated. The first would be to add a require statement to the top of the function. This would mitigate this specific instance of this exception, though the potential exception still lurks within the logic of the function.

    Another option would be to create a safer math library that can be used to safely perform operations that could head off these potential exceptions. Rerunning the unit test command, you will see that there is still an Exception thrown, but with the message of the require condition provided in the sub() function. Remember that functions that have been reverted by a require condition will refund any remaining gas not yet consumed. As compared to a panic exception that will not refund the remaining gas. Rerunning the unit test command, you should now have a passing unit test.

    When you are ready, click the Next step > to continue.

  3. Challenge

    Event Logging and Querying in Smart Contracts

    One of the key benefits of a smart contract is its ability to store information on an immutable ledger. Although the Tickets contract is relatively simple, it demonstrates the fundamental functionality of maintaining state across various accounts. Once a caller claims their ticket, that claim will be permanently recorded on the EVM, unless the contract explicitly provides functionality to reverse it.

    As the contract currently stands, only the individual caller can modify and review their particular state, specifically to claim and check their ticket claim. However, when building a web3 decentralized application (DApp), it's often essential for the application to be aware of certain events, such as when a ticket is claimed. This is where the Ethereum network's ability to store events in a public log comes into play, as it allows for subscription or querying of these logs.

    Your DApp can either subscribe to this log to receive real-time notifications of specific events, or it can query the logs to search for particular historical events. In fact, some DApps utilize these logs to perform various functions, such as maintaining information off-chain to avoid incurring gas costs, or to delegate some computational work to external systems. This might include subscribing to the event log and triggering external processes, like a fulfillment process, thus creating a bridge between on-chain and off-chain functionalities. To observe this firsthand, you will once again leverage HardHat's unit testing capabilities. However, similar results can be achieved using any JavaScript client that employs a web3 library, like web3.js. For this demonstration, we're using HardHat's ethers.js variant. While there might be minor modifications required for direct implementation in a real-world web3 DApp, the core concepts remain consistent.

    The unit test file test/StepThreeTests.js contains another faux unit test to help demonstrate reading these events from the logs. You have seen the first part of the Query from Contract unit test before where the contract is deployed and a ticket is claimed. The logs are then accessed through the contract to filter for the specific TicketClaimed event. Querying the logs through the contract has hidden some of the complexities of querying logs, such as identifying the network provider, which Hardhat has encapsulated in the contract's deploy() function.

    Additionally, the event's signature, TicketClaimed(address), has been encoded and stored in the topics array of the log. In fact, almost all aspects of the log are stored as hashes of the actual value. The human-readable elements of interface, fragment, and args in the returned EventLog have been conveniently added by the queryFilter() method.

    As you proceed in your Solidity learning, you can explore these complexities through the provider.getLogs() method.

    When you are ready, click the Next step > to continue.

  4. Challenge

    Enhancing Flexibility with Function Overloading

    As mentioned in the introduction, the ticketClaimed() function is particularly helpful for the caller, as it checks whether the caller has claimed a ticket. An alternate version of this function, taking an address as a parameter to verify whether that address has claimed a ticket, would also be beneficial. Providing multiple functions with different parameters, referred to as function overloading, enables more flexible usage of the function within the contract.

    For this step, you'll take a different approach. Opening the test/StepFourTests.js file, you'll see unit tests for both overloaded versions of the ticketClaimed() function. Unsurprisingly, the second unit test is failing, since the overload ticketClaimed(address _account) function has not been created, although the reason it is failing might surprise you.

    The first expect assertion is trying to call the overload function ticketClaimed(address _account), which again has not yet been created, using the owner account. It passes in addr[0] to check if a ticket has been claimed. Currently this is returning false, but once the overloaded function is created, this should return true.

    The second assertion calls the function with the addr[0] account, checking if the owner has claimed a ticket. This should return false, as the owner account has not claimed a ticket. However, if you comment out the first assertion and rerun the test, you'll find that this call returns true. It seems that the call is ignoring the parameter and invoking the parameterless ticketClaimed() function, essentially checking if the caller addr[0] has claimed a ticket.

    When working with overloaded functions in smart contracts, it's crucial to ensure that the functions are called with the exact expected number of parameters. A mismatch in parameters can lead to unexpected behavior and serious issues in your contract's functionality.

    You can uncomment the first expect assertion and proceed with creating the overloaded version of ticketClaimed() that takes a parameter. Rerunning the unit test file:

    npx hardhat test test/StepFourTests.js
    

    You are now presented with the error message:

    TypeError: ambiguous function description

    (You may need to scroll up to see this.)

    It appears that ethers.js does not directly support the ambiguity of overloaded functions. Although it does provide a means to explicitly call the overloaded function. Rerunning the unit test file should now succeed:

    npx hardhat test test/StepFourTests.js
    

    Solidity does support function overloading, allowing multiple functions with the same name but different parameters. It appears that ethers.js javascript framework requires an additional step in order to be able to call them. Due to this complexity, you may want to carefully consider whether this additional effort makes sense for your specific implementation. Weighing the benefits against potential confusion or errors will help you make an informed decision.

    When you are ready, click the Next step > to continue.

  5. Challenge

    Next Steps

    Congratulations on completing the first part of this Exploring Advanced Solidity Concepts Lab.

    In Part 2 of this lab you will explore how to export common functionality so that it can be shared across functions, contracts, and even across different files.

    Hope to see you soon in the next lab, "Guided: Exploring Advanced Solidity Concepts - Part 2".

    Here's to your continued success in Blockchain training and Solidity contract development! We look forward to seeing what you will create.

Jeff Hopper is a polyglot solution developer with over 20 years of experience across several business domains. He has enjoyed many of those years focusing on the .Net stack.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.