(Part 5) Ethereum Solidity - Custom Access Modifiers, Security Breach Alerts, Assert And Require(PT 5)

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"
image.png

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.

image.png

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
image.png

But now, I have changed the virtual address to 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c
image.png

And if I try to change the variable it throws me a very ugly error and the variable is not changed

image.png

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
image.png

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!!!!

image.png
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 modifier
  • securityBreach event
  • notOwnerChange function
  • ownerChange 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

image.png

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

image.png

Curriculum

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

H2
H3
H4
Upload from PC
Video gallery
3 columns
2 columns
1 column
5 Comments