This write-up will explain my accidental discovery of a solidity compiler bug. During a security audit on a protocol, I came across an interesting and Known compiler bug on solidity. I quickly escalated to the Ethereum security team and got a well-expected response.
Compiler version affected:
0.8.19+commit.7dd6d404 and the latest release
Target EVM Version:
Default on Remix
Framework/IDE:
Remix IDE
Solidity version 0.8.0 or above promises to prevent arithmetic overflow and underflow errors by default.
But In certain circumstances, It throws unexpected results by producing large Unsigned integers when it is used with a Signed Integer. (int256)
I came across a protocol that uses signed and unsigned integers for subtracting, where its VARIABLE A and B are declared as int256 and subtraction is performed inside the type of unsigned integers as uint256(A - B).
Here I got an idea to test for arithmetic error, as I expected a large number to be produced by declaring Variable A lesser than Variable B and thus solidity program doesn't revert and produces (uint256.max) - (-C).
Here (-C) is the output value of int256 A - B.
A protocol, that I was involved in a security audit, uses it for reward calculation and fortunately, it is not exploitable by the nature of its design. ๐ฎโ๐จ
Sample of Pseudovulnerable code:
struct UserInfo {
int256 rewardDebt;
}
int256 _accumulatedReward = int256((user.amount * accRewardPerShare) / ACC_REWARD_PRECISION);
uint256 _pendingRewards = uint256(_accumulatedReward - user.rewardDebt);
Input program that triggers the bug:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract testMath {
//Numbers to Test
int256 a = 1;
int256 b = 10;
// This will be succeeded and Returns (uint256.max - 9)
function tests() public returns (uint256) {
uint256 o = uint256(a - b);
return o;
}
}
Expected Behaviour:
The uint256(A - B) should be aliased as uint256(a) - uint256(b) whether it's written as uint256(A-B).
pragma solidity ^0.8.0;
contract testMathRevert {
//Numbers to Test
int256 a = 1;
int256 b = 10;
// This will be Reverted;
function tests() public returns (uint256) {
uint256 o = uint256(a) - uint256(b);
return o;
}
}
Response from the Ethereum Security team:
This is a known issue and has been previously reported. It is scheduled to be addressed eventually to improve the situation for conversions, making different conversion behaviour (e.g. truncating vs reverting) explicit: https://github.com/ethereum/solidity/issues/11284