Repository
https://github.com/igormuba/EthereumSolidityClasses/tree/master/part4
What Will I Learn?
- Custom access modifiers
- Security alerts using access modifiers and events
- Difference between custom errors generated by assert Vs by require
Requirements
- Internet connection
- Code editor
- Browser
Difficulty
- Intermediate
Tutorial Contents
On the previous tutorial I have mentioned we have a few access modifiers, as a quick review, they are
- public: anyone anywhere can access it
- private: only the contract itself can access it, children and external ones not
- external:only external source can access it, the contract itself can not, not used very often
- internal: only the contract and it's children can access it
But, thankfully, to give us flexibility the Solidity developers gave us the ability to create personalized, or custom as they call it, access modifiers, let us make a simple contract to test how it works!
The contract without custom access modifiers
pragma solidity ^0.5.0;
contract customModifier {
address private owner; //owner of the contract
uint public ownerVariable; //a number chosed by the owner
constructor() public{
owner = msg.sender; //sets the owner to be the deployer/creator of the contract
}
function setOwnerVariable(uint number) public{
ownerVariable = number; //sets the variable to a chosen number
}
function getOwnnerVariable() public view returns (uint){
return ownerVariable; //gets the set variable
}
}
As the code above is at the moment, we should have designed the contract so that only the contract owner could change the ownerVariable
, but as it is now, anyone with the address of the contract could call the function setOwnerVariable
and change it, imagine if that was the balance of a token, what a mess!
We could manually set a requirement so that the function setOwnerVariable
could be called by anyone, but the code inside it would have a verification to see if the caller is the owner, like in
function setOwnerVariable(uint number) public{
if (msg.sender == owner){ //verification if the caller is the owner
ownerVariable = number;
}
}
Well, that sounds feasible enough, but the #1 rule of programming is to avoid repetition! Specially in Ethereum where extra or inefficient code (we will talk about cost efficiency soon) literally costs money! Imagine if we have hundreds of functions and we had to add that kind of validation for every single function!! That would result in extra unnecessary code and extra gas (ETH) cost!
Creating a custom modifier
To avoid code repetition and to reduce transaction costs, a great and very useful tool for Solidity developers is the modifier
, here is how it works
modifier ownerOnly {
require(msg.sender == owner); //same as the if above
_; //tells that this modifier should be executed before the code
}
function setOwnerVariable(uint number) public ownerOnly{//with public + ownerOnly custom modifier
ownerVariable = number;
}
The code above has the addition of a custom modifier using the keyword modifier
, inside it we put the validations required, you could, for example, inside this modifier check balances, etc, and that would be very helpful on bigger projects, but to keep simple we are just checking if the caller of the function setOwnerVariable
is the real owner of the contract. It is like if you are creating your own keyword because you will add it on the declaration of the function, right after the native access modifier declaration.
By doing so, you are telling the Ethereum Virtual Machine that before getting inside the brackets it should run the validations inside our ownerOnly
.
Assert Vs. Require
When writing a code for the Ethereum EVM it is very important to know what errors could occur and to prepare to handle them because you are working with the user's money, I can not stress it enough.
Above we have used the keyword require
to make sure that a logical condition is met, but we could also abort and revert the execution of a function if another error, not related to the input and the logic, occur.
According to the docs
https://solidity.readthedocs.io/en/develop/miscellaneous.html
On the section "Global variables"
The main argument for using require
instead of assert
is that the require
reverts the operations done AND REFUNDS THE GAS LEFT, while the assert
function CONSUMES ALL THE GAS LEFT (I bet the miners are thankful for programmers that abuse the assert function)
To read more about the bytecode and the underlying working of those functions, read the Ethereum Improvement Proposal 140
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-140.md
That implements in bytecode the revert
function.
How the custom error occurs
Now that we have implemented the access modifier and understood how it works, let us see how it works in practice.
Using the JavaScript virtual machine in the test environment of Remix, I have deployed the contract using the virtual Ethereum address of 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
If I change and retrieve the `ownerVariable using the owner address, everything works exactly as expected
But now, I have changed the virtual address to 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
And if I try to change the variable it throws me a very ugly error and the variable is not changed
The price of assert (AVOID USING IT!!)
In the past, the error would be something saying that the opcode is invalid (check EIP140 proposal linked above), but on recent versions of solidity, with the implementation of the revert function, the error explains better what happened and saves you some gas!
Check the costs
If you change require to assert
modifier ownerOnly {
assert(msg.sender == owner);
_;
}
and deploy the contract again, in my case, again I deployed using the address 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
changed the variable, retrieved and it works, then I changed it to 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
to show you how the assert error is handled
AND LOOK AT THAT GAS COST!!!!
It gives all the transaction costs to the miners!
You CAN'T put a custom access modifier on events
Putting any access modifier other than public on an event will result on errors since events are designed to alert the blockchain about something that has occurred on your contract, so, if you intend to create some sort of private event, the most you can do is put some logic inside another function.
Using modifier + event to create security alerts!
In this example I will show one way that you could merge together events and custom modifiers to alert the whole network in case there is a security breach in your contract.
The code is similar to what we have already done before, but I will add the following
nowOwnet
modifiersecurityBreach
eventnotOwnerChange
functionownerChange
function
And change the setOwnerVariable
Starting with the setOwnerVariable
which is the "main function" that will control the flow of the security check.
Instead of right away checking if the sender is the owner and throwing an error and reverting everything in case it is not, we will use an if else to see if the sender is the owner
function setOwnerVariable(uint number) public{
if (msg.sender == owner){//do this if the caller is the owner
ownerChange(number);//function to be defined of what to do
}
else{//do this if the caller is not the owner
notOwnerChange(msg.sender, number); //function to be defined to alert the network of what happened
}
}
We need to use an if/else statement because if we put a modifier to check if the msg.sender
is the owner or not on the declaration of the function and the sender is not the owner, we want to be able to send an event, and if it is not the owner we will not be able to send an event because everything will be reverted!
Now we will host the former logic of the setOwnerVariable
inside a new function that will basically follow the normal flow it did before in case the caller is the owner
function ownerChange(uint number) private ownerOnly{
ownerVariable=number;
}
But if the caller is not the owner, that is, in case someone is trying to change some data in the contract without authorization, the function `notOwnerChange will be called, and it should look something like this
function notOwnerChange(address hacker, uint number) private notOwner{ //notOwner modifier to be defined
emit securityBreach(hacker, number); //event to be defined alerting the network of who tried to change to what
}
The modifier that will be checked against in this function is the notOwner
, it works as a double check, and the modifier looks like
modifier notOwner{
require(msg.sender!=owner);//checks again if the sender is indeed not the owner
_;
}
We are double checking because the previous validation was a simple else
and we want to make sure we don't alarm the network for something that was not indeed a breach
And in last, if the two verification confirm, we emit the event, that is inherently visible for all the network, that tells what address tried to do what change
event securityBreach(address hacker, uint number);
How our security alert looks like
Now, on remix, let us see how the security breach looks like.
I am deploying the contract with the address 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
And when trying to use it from the real owner the results are the exact same as before, so I will omit the results of the changes because it worked without raising any events, just like before
But now, I have switched to the address 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
And if I call the function the transaction works (does not revert), but the change is not made and the whole network gets an alert of who tried to do what
BONUS: If you are sure that when that function is called is because someone tried to hack, you can possibly change the require
to assert
and make the hacker lose all his gas ;)
Beneficiaries
This post has as beneficiaries
@steempeak with 5%
@utopian.pay with 5%
using the SteemPeak beneficiary tool
Curriculum
- (Part 4) Ethereum Solidity Development - Inheritance,Working With Multiple Contracts And Simple Mutability(PT 4)
- (Part 3) Ethereum Solidity Development - Contract Mutability, DelegateCall And Calling Functions By Address(PT 3)
- (Part 2) Ethereum Solidity Development - Deploying, Securiy And ERC20 Compliance(PT 2)
- (Part 1) Ethereum Solidity Development - Getting Started + Lower Level Explanation (PT 1)
I AM A STEEM WITNESS
To vote for me, simply use the SteemConnect link below
https://steemconnect.com/sign/account-witness-vote?witness=igormuba&approve=1
or go to https://steemit.com/~witnesses
(note, it is /~witnesses, not /witness)
Scroll all the way down to "If you would like to vote for a witness outside of the top 100, enter the account name below to cast a vote."
type igormuba and click vote