Underflow and Overflow
There are no excessively large numbers in mathematical logic. Any two significant numbers can be added together to obtain an accurate result. Numbers don’t overflow thanks to high-level programming languages like JavaScript and Python. Although the result in some circumstances might be endless when two positive numbers are added it might never produce a negative result. Floating-point values do not overflow in Java or C++, whereas integer numbers do.
In contrast to numerous other programming languages, Solidity’s integer data types are not actual integers. Although they resemble integers when the quantities are tiny, they are impossible to express arbitrary huge numbers.
The outcome of the sum is too big and it can not be stored in the data type uint8, and the limit of uint8 is 2⁸-1, or 255, hence the following code results in an overflow:
// Default: Checks for underflow or overflow
uint8 x = 255;
uint8 y = 1;
return x + y; //reverts the execution with an error
// Wrapped in unchecked: does not verify for underflow or overflow
unchecked {
uint8 x = 255;
uint8 y = 1;
return x + y; //returns 0
}
Prior to Solidity 0.8.0, checks for integer overflows and underflows were made using the SafeMath package. Solidity 0.8.0 and newer versions conduct that check for us thanks to the compiler. Unchecked and Checked, or “wrapping” mode, are two methods the compiler can manage these overflows.
Overflows are recognized by the default “checked” mode, which results in a failed assertion. This check can be turned off with “unchecked …,” which will cause the overflow to be quietly disregarded. If the above code were enclosed in the “unchecked { … }” code block, it would yield 0.
Even in checked mode, do not think you are secure from overflow issues. This mode is the default for overflows. If the overflow can indeed be averted, a smart contract may become stuck in a certain state where it fails all executions. That’s why you must always account for the possibility that a variable will exceed or fall short of the limit you’ve set by writing checks. In an effort to identify probable overflows, try to employ a requirement that limits input size to an acceptable range. Here is a portion of the V2 core contract for Uniswap. This tutorial is not intended to cover the function’s business rules, but you see how the _update function’s developers specifically required user input:
// update reserves and, on the first call per block, price accumulators
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
// Check out how they have explicitly put a require to the user input in the function:
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast;
// overflow is desired
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
When is the Unchecked wrapping suitable?
To ensure that the arithmetic you employ in your smart contract will not overflow or underflow, Solidity runs an extra check. These extra processes raise the contract’s opcode and raise the gas cost. We can instruct the compiler to forgo verification for overflow and underflow if we are confident that the arithmetic operations we will carry out as an element of the contract will not have either of these outcomes. This can enable the user of the contract to conserve gas while carrying out certain tasks. Take as an example the code line that follows:
function foo(uint8 _limit) public {
for (uint i; i < _limit;) {
// do something that doesn't change the value of i
unchecked { ++i; }
}
}
Because we have limited the value of the loop’s post conditional, I to strictly fall within the range of the uint8 limit variables, it is not necessary to verify for overflow or underflow in this case. The compiler already verifies that the limit does not exceed or fall below its bounds. Thus, if the limit exceeds 28¹, or 255, an error would be raised. Therefore, it’s value cannot exceed 28–2, or 254. Therefore, without compromising security, it is safe to place the ++I increment underneath the unchecked {…} wrap. This tiny tip could spare as much as 30 to 40 grams of gas per loop, which can add up if there are many iterations.
Bonus
Observe how we declared i in the iteration but did not initialize it in the example above. This is due to the fact that, in contrast to other languages, the default value of a Uint Solidity is 0 and just not null. We can avoid wasting fuel by not explicitly initialization the variable.
Take note of the substitution of ++i for i++. For an Unsigned integer, ++i uses less gas than i++ or i+= 1 since pre-increment is less expensive. Regardless of whether the optimizer is turned on, this statement is accurate.
Join our beta program now https://www.olympix.ai/
Originally published at https://www.linkedin.com.