You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: learn_evm/arithmetic-checks.md
+24-24Lines changed: 24 additions & 24 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -65,6 +65,7 @@ If `a` exceeds this value, we can conclude that `a + b` will overflow.
65
65
An alternative (and slightly more efficient) approach to computing the maximum value of `a` is to invert the bits on `b`.
66
66
67
67
```solidity
68
+
/// @notice versions >=0.8.0 && <0.8.16 with compiler optimizations
68
69
function checkedAddUint2(uint256 a, uint256 b) public pure returns (uint256 c) {
69
70
unchecked {
70
71
c = a + b;
@@ -74,8 +75,6 @@ function checkedAddUint2(uint256 a, uint256 b) public pure returns (uint256 c) {
74
75
}
75
76
```
76
77
77
-
> This code is produced by the compiler with optimizations turned on for versions <0.8.16.
78
-
79
78
This is equivalent, because `type(uint256).max` is a 256-bit integer with all its bits set to `1`.
80
79
Subtracting `b` from `type(uint256).max` can be viewed as inverting each bit in `b`.
81
80
This can be demonstrated by the transformation `~b = ~(0 ^ b) = ~0 ^ b = MAX ^ b = MAX - b`.
@@ -218,6 +217,7 @@ function checkedAddInt2(int256 a, int256 b) public pure returns (int256 c) {
218
217
```
219
218
220
219
Nevertheless, by utilizing `xor`, which is the bitwise exclusive-or operation, we can combine these checks into one.
220
+
The code is written in assembly, because Solidity lacks an `xor` operation for boolean values.
221
221
222
222
```solidity
223
223
function checkedAddInt3(int256 a, int256 b) public pure returns (int256 c) {
@@ -238,8 +238,6 @@ function checkedAddInt3(int256 a, int256 b) public pure returns (int256 c) {
238
238
}
239
239
```
240
240
241
-
The code is written in assembly, because Solidity lacks an `xor` operation for boolean values.
242
-
243
241
A different approach to detecting overflow in addition is to observe that adding two integers with different signs will never overflow.
244
242
This reduces the check to the case when both summands have the same sign.
245
243
If the sign of the sum is different from one of the summands, the result has overflowed.
@@ -279,6 +277,7 @@ function checkedSubUint(uint256 a, uint256 b) public pure returns (uint256 c) {
279
277
280
278
We could, like before, perform the check on the result itself using `if (c > a) arithmeticError();`, because subtracting a positive value from `a` should yield a value less than or equal to `a`.
281
279
However, in this case, we don't save any operations.
280
+
282
281
Just as with addition, for signed integers, we can combine the checks for both scenarios into a single check using `xor`.
283
282
284
283
```solidity
@@ -316,7 +315,7 @@ function checkedMulUint1(uint256 a, uint256 b) public pure returns (uint256 c) {
316
315
317
316
> It's important to note that the Solidity compiler always includes a division by zero check for all division and modulo operations, regardless of the presence of an unchecked block.
318
317
> The EVM itself simply returns `0` when dividing by `0`, and this also applies to inline-assembly.
319
-
> If the order of the boolean expressions is evaluated in reverse order, it could cause an arithmetic check to incorrectly revert when a=0.
318
+
> If the order of the boolean expressions is evaluated in reverse order, it could cause an arithmetic check to incorrectly revert when `a = 0`.
320
319
321
320
We can compute the maximum value for `b` as long as `a` is non-zero. However, if `a` is zero, we know that the result will be zero as well, and there is no need to check for overflow.
322
321
Like before, we can also make use of the result and try to reconstruct one multiplicand from it. This is possible if the product didn't overflow and the first multiplicand is non-zero.
@@ -332,7 +331,7 @@ function checkedMulUint2(uint256 a, uint256 b) public pure returns (uint256 c) {
332
331
}
333
332
```
334
333
335
-
For reference, we can further remove the addition division by zero check by writing the code in assembly.
334
+
For reference, we can further remove the additional division by zero check by writing the code in assembly.
336
335
337
336
```solidity
338
337
function checkedMulUint3(uint256 a, uint256 b) public pure returns (uint256 c) {
@@ -371,7 +370,7 @@ function checkedMulInt(int256 a, int256 b) public pure returns (int256 c) {
371
370
}
372
371
```
373
372
374
-
Newer Solidity versions optimize the process by using the computed product.
373
+
Newer Solidity versions optimize the process by utilizing the computed product in the check.
375
374
376
375
```solidity
377
376
/// @notice versions >=0.8.17
@@ -385,8 +384,8 @@ function checkedMulInt2(int256 a, int256 b) public pure returns (int256 c) {
385
384
}
386
385
```
387
386
388
-
When it comes to integer multiplication, it's important to check for`a < 0` and `b == type(int256).min`.
389
-
The actual caseis limited to `a == -1` and `b == type(int256).min`, where the product `c` will overflow.
387
+
When it comes to integer multiplication, it's important to handle the case hen`a < 0` and `b == type(int256).min`.
388
+
The actual case, where the product `c` will overflow, is limited to `a == -1` and `b == type(int256).min`.
390
389
This is because `-b` cannot be represented as a positive signed integer, as previously mentioned.
391
390
392
391
## Arithmetic checks for addition with sub-32-byte types
@@ -402,7 +401,8 @@ On a 64-bit system, integer addition works in the same way as before.
402
401
= 0x0000000000000001 // int64(1)
403
402
```
404
403
405
-
However, when performing the same calculations on a 256-bit machine, we need to extend the sign of the int64 value over all unused bits.
404
+
However, when performing the same calculations on a 256-bit machine, we need to extend the sign of the int64 value over all unused bits,
405
+
otherwise the value won't be interpreted correctly.
406
406
407
407
```solidity
408
408
extended sign ──┐┌── 64-bit information
@@ -422,11 +422,11 @@ For example, when performing addition without knowing what the upper bits are se
It is crucial to be mindful of when to clean the bits before and after operations.
426
-
By default, Solidity takes care of cleaning the bits before operations on smaller types and lets the optimizer remove any redundant steps.
427
-
However, values accessed after operations included by the compiler are not guaranteed to be clean. In particular, this is the case for addition with small data types.
428
-
The bit cleaning steps will be removed by the compiler, even without optimizations, if a variable is only accessed in a subsequent assembly block.
429
-
Refer to the [Solidity documentation](https://docs.soliditylang.org/en/v0.8.18/internals/variable_cleanup.html#cleaning-up-variables) for further information on this matter.
425
+
> It is crucial to be mindful of when to clean the bits before and after operations.
426
+
> By default, Solidity takes care of cleaning the bits before operations on smaller types and lets the optimizer remove any redundant steps.
427
+
> However, values accessed after operations included by the compiler are not guaranteed to be clean. In particular, this is the case for addition with small data types.
428
+
> The bit cleaning steps will be removed by the optimizer (even without optimizations enabled) if a variable is only accessed in a subsequent assembly block.
429
+
> Refer to the [Solidity documentation](https://docs.soliditylang.org/en/v0.8.18/internals/variable_cleanup.html#cleaning-up-variables) for further information on this matter.
430
430
431
431
When performing arithmetic checks in the same way as before, it is necessary to include a step to clean the bits on the sum.
432
432
One approach to achieve this is by performing `signextend(7, value)`, which extends the sign of a 64-bit (7 + 1 = 8 bytes) integer over all upper bits.
@@ -454,7 +454,7 @@ function checkedAddInt64_1(int64 a, int64 b) public pure returns (int64 c) {
454
454
```
455
455
456
456
If we remove the line that includes `c := signextend(7, c)` the overflow check will not function correctly.
457
-
This is because Solidity does not take into account the fact that the variable is used in an assembly block, so the compiler removes the bit cleaning operation, even if the Yul code includes it after the addition.
457
+
This is because Solidity does not take into account the fact that the variable is used in an assembly block, so the optimizer removes the bit cleaning operation, even if the Yul code includes it after the addition.
458
458
459
459
One thing to keep in mind is that since we are performing a 64-bit addition in 256 bits, we practically have access to the carry/overflow bits.
460
460
If our computed value does not overflow, then it will fall within the correct bounds `type(int64).min <= c <= type(int64).max`.
@@ -478,7 +478,7 @@ function checkedAddInt64_2(int64 a, int64 b) public pure returns (int64 c) {
478
478
479
479
There are a few ways to verify that the result in its 256-bit representation will fit into the expected data type.
480
480
This is only true when all upper bits are the same.
481
-
The most direct method, as with integer overflow, involves checking the lower and upper bounds of the value.
481
+
The most direct method, as just shown, involves checking the lower and upper bounds of the value.
482
482
483
483
```solidity
484
484
/// @notice Check used in int64 addition for version >= 0.8.16.
@@ -487,7 +487,7 @@ function overflowInt64(int256 value) public pure returns (bool overflow) {
487
487
}
488
488
```
489
489
490
-
We can simplify the comparison process to a single operator if we're able to shift the disjointed number domain back so that it's connected.
490
+
We can simplify the expression to a single comparison if we're able to shift the disjointed number domain back so that it's connected.
491
491
To accomplish this, we subtract the smallest negative int64 `type(int64).min` from a value (or add the underlying unsigned value).
492
492
A better way to understand this is by visualizing the signed integer number domain in relation to the unsigned domain (which is demonstrated here using int128).
493
493
@@ -530,7 +530,7 @@ function overflowInt64_2(int256 value) public pure returns (bool overflow) {
530
530
}
531
531
```
532
532
533
-
In this case the verbose assembly code might actually be easier to follow than the Solidity code which can sometimes contain implicit operations.
533
+
In this case the verbose assembly code might actually be easier to follow than the Solidity code which sometimes contains implicit operations.
534
534
535
535
```solidity
536
536
int64 constant INT64_MIN = -0x8000000000000000;
@@ -544,7 +544,7 @@ function overflowInt64_2_yul(int256 value) public pure returns (bool overflow) {
544
544
```
545
545
546
546
As mentioned earlier, this approach is only effective for negative numbers when all of their upper bits are set to `1`, allowing us to overflow back into the positive domain.
547
-
An alternative and more straightforward method would be to verify that all of the upper bits are equivalent to the sign bit.
547
+
An alternative and more straightforward method would be to simply verify that all of the upper bits are equivalent to the sign bit for all integers.
548
548
549
549
```solidity
550
550
function overflowInt64_3(int256 value) public pure returns (bool overflow) {
@@ -618,7 +618,7 @@ function checkedMulInt64(int64 a, int64 b) public pure returns (int64 c) {
618
618
619
619
However, if the maximum value of a product exceeds 256 bits, then this method won't be effective.
620
620
This happens, for instance, when working with int192. The product `type(int192).min * type(int192).min` requires 192 + 192 = 384 bits to be stored, which exceeds the maximum of 256 bits.
621
-
Overflow occurs in 256 bits, resulting in a `0`, and it won't be logical to check if the result fits into 192 bits.
621
+
Overflow occurs in 256 bits, which loses information, and it won't be logical to check if the result fits into 192 bits.
622
622
In this scenario, we can depend on the previous checks and, for example, attempt to reconstruct one of the multiplicands.
623
623
624
624
```solidity
@@ -636,16 +636,16 @@ Once more, we must consider the two special circumstances:
636
636
1. When one of the multiplicands is zero (`a == 0`), the other multiplicand cannot be retrieved. However, this case never results in overflow.
637
637
2. Even if the multiplication is correct in 256 bits, the calculation overflows when only examining the least-significant 192 bits if the first multiplicand is minus one (`a = -1`) and the other multiplicand is the minimum value.
638
638
639
-
The second exceptional scenario is still relevant since it is possible to encounter overflow when only considering the least-significant 192 bits, despite the multiplication being correct in 256 bits.
0 commit comments