When one seeks to distinguish between Web2 and Web3, a key value that differentiates the two is the concept of ownership.
Simply put, what you create is what you own and can monetize. Just as it should be. Nothing less, nothing more. In fact, the day you mint your first NFT is when you’ve made it in Web3 as an owner, thanks to the immutability of the blockchain. If anything, the feeling of being protected is priceless.
Speaking of which, this concept of ownership is just as important to smart contracts in respect to accessible functions and permitted state change.
So, why does it matter that you ‘own’ the smart contract that you’ve written?
Think about it: if you owned a bike, would you hesitate to keep ownership records? Just in case someone stole and used it for nefarious purposes? Of course, not.
It’s for the very same reason why you would want to prove your ownership of an NFT or a smart contract. Well, someone might just gain unauthorized access and benefit financially from it, right?
Let’s say you work with Dunzo and have written this smart contract where you can set the package price plus delivery charges along with whether it was paid or not by the buyer.
In the ideal scenario, you can set the buyer’s name, the total price of the package and delivery to a certain amount and set the setPackageDelivered to true. Once you’re done delivering the package, of course.
But, as we all know, nothing ever goes to plan.
Now, what if the buyer had access to the contract functions and the ability to reset the packageDelivered value to false or change the packageDeliveryPrice value to a lesser one? Not only can they pay less for the product but get away with not paying anything at all.
Clearly, apart from Dunzo, no one else should have access to make such changes, and which is why setting access control matters.
Now, let’s deploy this smart contract and see how easy it is for an intruder to alter the values in the state variables.
With both the setPrice and setPackageDelivered functions accessible, anyone can make changes that can result in financial loss for Dunzo delivery.
If Dunzo had set the original price of the item at 5 ETH, the customer can now alter that value to 3 ETH. Of course, since the customer can also access the setPackageDelivered function and change the boolean value to false, he might receive another delivery of the same item. So, in either case, Dunzo stands to lose money, and might not even realize it until it is too late.
For example, let’s say that the product being delivered is worth 5 ETH, as shown below:
Upon deployment, no one but Dunzo should be able to modify contract terms. Still, when we switch addresses in Remix and reset the price to 3, this is possible due to no protection in place.
We can even change the setPackageDelivered status from true to false. Even if the package has already been delivered to the customer.
As you can tell, it’s important to distinguish between the owner and other users for said smart contract. So, let’s look at 4 fixes that will set the proper access control for this smart contract and ensure its safety as a result.
So, how can one differentiate between the owner and other users when writing smart contracts?
Clearly, this is necessary due to the financial loss that can be incurred if ignored.
Method #1: Use a require statement
As you can see in the code below, a new state variable owner of address type has been added along with a ‘require’ statement. In the constructor, the owner state variable stores the address that is used when deploying the contract. As you know constructors in smart contracts are invoked only once, so the address cannot be changed.
Now, when you invoke the setPrice function with a lesser value and a different address, the transaction is reverted to the initial state, much like the message below:
As you can see, the reason provided by the contract involves the error message that we added to the ‘require’ statement. That said, no one but the owner of the contract — Dunzo, in this case — can change the sales terms.
Method #2: Use a function modifier
As simple as it is to add a ‘require’ statement with the right condition, a modifier is a block of code that you can use repeatedly. This certainly makes much more sense than writing numerous ‘require’ statements for such a check.
In the code shared earlier, only the setPrice function is protected by a ‘require’ statement but nothing has been done for the setPackageDelivered function here. Using a modifier should do the trick, as shown in the Solidity code below:
Now, if you try to access either the setPrice or setPackageDelivered functions, you’ll get the same error message obtained earlier, and as shown below:
Method #3: Ownable
This fix requires you to use the Ownable smart contract provided by OpenZeppelin. First, you have to import the Ownable smart contract and then add the onlyOwner modifier to the functions that you would like to protect. So, as shown below, both the setPrice and setPackageDelivered functions have the onlyOwner modifier below.
Now, since the dunzoDelivery smart contract will use some of the functions in Ownable.sol, the “is Ownable” is added to the contract name too.
That said, there are two other functions that one can access when using the Ownable smart contract: renounceOwnership and transferOwnership. While the account that was used to deploy the contract is usually considered to be the owner, this can be changed using these functions.
Apart from seeing the owner’s address and the other agreement details, you can see the renounceOwnership and transferOwnership functions for use below:
As expected, when you try to invoke the setPrice function using another address, the transaction will not go through. Instead, it reverts to the original state, with the reason provided below:
Using the Ownable smart contract is good if you require only a single owner among other users. If you’re looking for more hierarchy, then the AccessControl.sol smart contract should be used.
Method #4: AccessControl
This last fix requires you to access the AccessControl smart contract by Open Zeppelin.
For starters, you have to add the Access Control hyperlink to the import statement as well as add “is AccessControl” to the contract statement.
As mentioned earlier, this smart contract allows you to add a number of roles within a hierarchy and which you must declare, as in the first two lines of the smart contract shown below:
In this contract, there are two roles namely Dunzo and Customer. Now, when you deploy the contract, you will have to assign the aforementioned declared roles to said addresses, so as to determine who can access what. In addition, two ‘require’ statements have been added to the setPrice and setPackageDelivered functions.
As you can tell, only the account that has been assigned the Dunzo role will be able to alter the sales terms mentioned during the time of deployment but not any other. That said, you can assign a number of roles with differing levels of access to functions of a smart contract using AccessControl.sol while Ownable.sol will not go into that much depth.
Now, these are not the only solutions available to implement access control in smart contracts. RBAC.sol and WhitelistCrowdSale.sol have also been available in the past and which can associate roles with addresses too, much like AccessControl.sol and Ownable.sol.
So, if you want to gain a thorough understanding as to the evolution of implementing access control, it will be well worth your time, going over code in these two smart contracts too.
Also published here.
The lead image for this article was generated by HackerNoon's AI Image Generator via the prompt "access denied biometric scan".