Root + Impact
Description
-
In function ERC20Internals::_burn there is a potential risk of underflow.
-
If the value of tokens burnt are higher than the user's balance. The final user's balance will be higher instead of lower as expected. Also, the total supply value of the contract could suffer underflow and get an incorrect value.
function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0x96c6fd1e))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
@> sstore(supplySlot, sub(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
@> sstore(accountBalanceSlot, sub(accountBalance, value))
}
}
Risk
Likelihood:
Impact:
Proof of Concept
Put below coding in Token.t.sol. It can be checked how the user's balance is highly increased (instead of decreasing).
function test_burn_underflow() public {
uint256 amount = 100e18;
address account = makeAddr("account");
token.mint(account, amount);
uint256 balanceBefore = token.balanceOf(account);
console.log("Balance before burn:", balanceBefore);
token.burn(account, amount + 50e18);
uint256 balanceAfter = token.balanceOf(account);
console.log("Balance after burn:", balanceAfter);
assert(balanceAfter > balanceBefore);
}
Recommended Mitigation
function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0x96c6fd1e))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
+ if lt(supply, value) {
+ revert(0, 0)
+ }
sstore(supplySlot, sub(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
+ if lt(accountBalance, value) {
+ revert(0, 0)
+ }
sstore(accountBalanceSlot, sub(accountBalance, value))
}
}