Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: medium

Missing Overflow Checks

Author Revealed upon completion

Description

  • The _mint function uses assembly add() for both _totalSupply and _balances without overflow checks.

  • While Solidity 0.8+ has built-in overflow protection, assembly blocks bypass this protection entirely. When add(x, y) exceeds type(uint256).max, it wraps around to a small number.

function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
// ...
let supply := sload(supplySlot)
// @> NO OVERFLOW CHECK: can wrap around
sstore(supplySlot, add(supply, value))
// ...
let accountBalance := sload(accountBalanceSlot)
// @> NO OVERFLOW CHECK: can wrap around
sstore(accountBalanceSlot, add(accountBalance, value))
}
}

Risk

Likelihood:

  • Requires minting very large amounts (close to type(uint256).max)

  • Less likely in normal operation but possible for malicious token deployers

  • More likely when token has 0 decimals or very large initial mint

Impact:

  • Total supply can wrap to near-zero after large mints

  • Individual balances can wrap, effectively burning tokens

  • Economic model completely broken

Proof of Concept

function test_mint_overflow() public {
uint256 largeAmount = type(uint256).max / 2;
// Mint large amount to alice
token.mint(alice, largeAmount);
assertEq(token.totalSupply(), largeAmount);
// Mint slightly more to bob - causes overflow
token.mint(bob, largeAmount + 1000);
// Total supply has wrapped around to a small number!
assertLt(token.totalSupply(), largeAmount);
// totalSupply ≈ 998 (wrapped around)
}

Recommended Mitigation

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 lt(newSupply, supply) {
+ mstore(0x00, shl(224, 0x94280d62)) // Or custom error
+ revert(0x00, 0x04)
+ }
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) {
+ mstore(0x00, shl(224, 0x94280d62)) // Or custom error
+ revert(0x00, 0x04)
+ }
sstore(accountBalanceSlot, newBalance)
+
+ // Emit Transfer event from address(0)
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0, account)
}
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!