[Ethereum] Revert reason for arithmetic overflows in Solidity v0.8

contract-designcontract-developmentsoliditysolidity-0.8.x

function nice(uint8 x) public returns(uint8 z) {
    uint8 z = 0;
    z = z + 240;
    require(z = z + x, "overflow");
}

I have been using this code with Solidity prior to the 0.8 update. If there was an overflow, the transaction would get reverted with the message overflow.

Now, I am moving to 0.8. If I use the same code, my test cases fail, because they don't get overfow revert message anymore. If I remove require and just leave z = z + x as the compiler suggests, I have no idea with what message it will get reverted with. What I want to achieve is to use 0.8 and still get overflow revert message.

NOTE I don't want to use unchecked.

Best Answer

Bit of Explanation

First you should familiarize yourself with the concept of "unchecked arithmetic", which is part of the v0.8 breaking changes list:

Arithmetic operations revert on underflow and overflow. You can use unchecked { ... } to use the previous wrapping behavior.

Importantly, overflows and underflows use the REVERT opcode instead of the INVALID opcode:

Failing assertions and other internal checks like division by zero or arithmetic overflow do not use the invalid opcode but instead the revert opcode.

If you're a user of Hardhat, you will get the following JavaScript/ TypeScript error in the terminal while testing your contract:

Error: VM Exception while processing transaction: reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)

It looks like Solidity chose the hexadecimal number 0x11 to be the panic code associated with arithmetic overflows and underflows.

Code Updated

Unfortunately there is no way to achieve what you want without wrapping your code in an unchecked block.

First you do that, and then you do what SafeMath does: check that the sum is greater than x; if it isn't, the operation has overflown max uint8.

function nice(uint8 x) public  returns(uint8 z){
    uint8 z = 0;
    z = z + 240;
    unchecked {
        z = z + x;
        require(z >= x, "Your custom message here");
    }
}

Suggestion

Why use revert reasons when you can now define custom errors?

error Overflow(uint8 z, uint8 x);

function nice(uint8 x) public  returns(uint8 z){
    uint8 z = 0;
    z = z + 240;
    unchecked {
        z = z + x;
        if (z < x) {
            revert Overflow(x, z);
        }
    }
}

Imo this is a godsend for Solidity development. Compared to revert reason strings, custom errors are easier to work with, more gas efficient, and more elegant.

Related Topic