Root + Impact
The _mint function uses Yul assembly for arithmetic operations which do NOT have Solidity 0.8+'s built-in overflow protection. This can cause silent integer overflow in totalSupply and account balances.
Description
Solidity 0.8+ automatically reverts on integer overflow/underflow. However, inline assembly (Yul) does NOT have this protection - arithmetic operations wrap around silently.
The _mint function uses add() for both totalSupply and accountBalance without overflow checks.
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
@> sstore(supplySlot, add(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
@> sstore(accountBalanceSlot, add(accountBalance, value))
}
}
Risk
Likelihood:
Requires minting large amounts close to type(uint256).max
Dependent on how implementing contracts expose mint functionality
Could happen accidentally with incorrect mint amounts
Impact:
totalSupply can wrap around to small values while actual circulating supply is huge
Individual balances can overflow and reset to near-zero
Complete breakdown of token accounting
Proof of Concept
Here is the PoC to mint tokens
function test_mintOverflow() public {
address account1 = makeAddr("account1");
address account2 = makeAddr("account2");
token.mint(account1, type(uint256).max - 1);
assertEq(token.totalSupply(), type(uint256).max - 1);
token.mint(account2, 2);
assertEq(token.totalSupply(), 0);
assertEq(token.balanceOf(account1), type(uint256).max - 1);
assertEq(token.balanceOf(account2), 2);
}
Recommended Mitigation
Add overflow checks in assembly before performing the addition.
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0xec442f05))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let balanceSlot := _balances.slot
let supplySlot := _totalSupply.slot
let supply := sload(supplySlot)
+ let newSupply := add(supply, value)
+ // Check for overflow: if newSupply < supply, overflow occurred
+ if lt(newSupply, supply) {
+ revert(0, 0)
+ }
- sstore(supplySlot, add(supply, value))
+ sstore(supplySlot, newSupply)
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
+ let newBalance := add(accountBalance, value)
+ // Check for overflow
+ if lt(newBalance, accountBalance) {
+ revert(0, 0)
+ }
- sstore(accountBalanceSlot, add(accountBalance, value))
+ sstore(accountBalanceSlot, newBalance)
}
}