Built-in "unchecked" modifier for functions

The default arithmetic overflow and underflow checks in Solidity v0.8 are great. One has to wrap some arithmetic operations in an unchecked block if one wishes to avoid the default checks. That is healthy - the compiler makes users err on the side of caution.

The downside is that this makes libraries verbose. For obvious reasons, library developers want to save as much gas as possible, which leads to lots of function that look like this:

function avg(uint256 x, uint256 y) internal pure returns (uint256 result) {
    unchecked {
        result = (x >> 1) + (y >> 1) + (x & y & 1);
    }
}

Or worse - see the log2 function in PRBMath.

My proposal is to add a new modifier that is built in the language called, say, unchecked, which can be applied directly to functions. With that we would not have to add an extra nested block when we want the entire body of the function to avoid the default arithmetic overflow and underflow checks. In fact, that might even lead to a bit of gas saved, since (at least in my head), not enabling the checks in the first place sounds cheaper than enabling them and disabling them immediately (what effectively happens when the entire body of the function is wrapped in unchecked).

What do you think?

2 Likes

In my opinion, a change of such a dramatic effect would be way too much hidden in the list of modifiers.

What you are doing here could be best achieved using inline assembly, in my opinion. Once we add custom operators, you could also define a non-checked integer type and maybe use this code:

function avg(uint256 x, uint256 y) internal pure returns (uint256 result) {
  UncheckedInt _x = toUnchecked(x);
  UncheckedInt _y = toUnchecked(y);
  result = toChecked((_x >> 1) + (_y >> 1) + (_x & _y & 1));
}

It is a bit more verbose, yes. The nice thing here, though, is also that if you forget to put the _ in front of one of the variables, the compiler will throw a type error at you because you cannot combine checked and non-checked integer types.
One issue we still do not yet have a good solution for, though, is how to actually apply the constant 1 with the operators.

Oh, I see. Looking forward to that then! I believe I could also use user defined value types and rewrite the code like this:

function avg(UncheckedInt x, UncheckedInt y) internal pure returns (UncheckedInt result) {
  result = (x >> 1) + (y >> 1) + (x & y & 1);
}

Assuming that you find a good way to handle constants like 1, of course.