Try   HackMD

Why you should revert early

Ethereum transactions that revert still have to pay the gas used up until the revert was triggered; If they run out of gas they pay the full limit. If they hits a revert opcode, probably from a require statement as a result of maybe an integer overflow check, they'd pay the gas up until the revert opcode was hit. They don't pay for the gas afterwards.

The implication is we should revert as early as possible in the execution to save the users gas incase a transaction reverts. Here are two (2) example contracts:

// DO NOT USE IN PRODUCTION
// USED FOR EXPLANATION ONLY
contract Example1 {

    uint256 public a;

    function simpleCheck(uint256 b) external {
        require(b < 4, "b must be less that 4");
        a = b;
    }
}

contract Example2 {

    uint256 public a;

    function simpleCheck(uint256 b) external {
        a = b;
        require(b < 4, "b must be less that 4");
    }
}

From the contractExample2 we would write to storage and then revert, while in Example1 we would revert and then write to storage. So executing the first function from Exapmle1 by calling simpleCheck(6); it's obvious that the function would revert since 6 is greater than 4. On executing this function the amout of gas used was 21,920.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Now, executing the simpleCheck function from Example2 which does SSTORE before reverting, the gas used was 44,034; the extra 20,000 gas is coming for the storage value a being written to.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

So I think that explains why you should do a revert quite early. Though there are situations where it is wise to do the check a little later and here is an example:

// DO NOT USE IN PRODUCTION
// USED FOR EXPLANATION ONLY
contract Example3 {

    uint256 public a;

    function multiplyOverflow(uint256 b) external {
        uint256 answer = a << b; // a * (2^b) 
        require(answer > a, "overflow");
    }
}

From Example3 above the function multiplyOverflow has a check after the multiplication and this is because it can get quite expensive to do a check for a multiplication function, so trying to anticipate if this function would revert might make it more expensive; Sometimes it is a good idea to do the check afterwards. In many cases where you use a contract from an external source it would be a great idea to do a check after executing a function from the contract, since there is a possibility that it can be mallicious.


If you found this helpful, consider following me on Twitter @0xosas.