Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: medium
Valid

# Lack of Overflow Checks in `_mint` Function Breaks Total Supply and Balance Integrity

Lack of Overflow Checks in _mint Function Breaks Total Supply and Balance Integrity

Description

In a secure ERC20 implementation, the _mint function must prevent arithmetic overflow by ensuring that _totalSupply + value and accountBalance + value do not exceed type(uint256).max.
However, this function directly uses Yul's add instruction for addition without validating the result for overflow. Since Yul's add silently wraps around on overflow, this can cause _totalSupply or account balances to be incorrectly set to extremely small values, thereby violating token supply conservation and account asset integrity.

function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
// ...
let supply := sload(supplySlot)
@> sstore(supplySlot, add(supply, value)) // No overflow check
// ...
let accountBalance := sload(accountBalanceSlot)
@> sstore(accountBalanceSlot, add(accountBalance, value)) // No overflow check
}
}

Risk

Likelihood:

  • Overflow is triggered when calling _mint again if the total token supply or an account balance is close to type(uint256).max (low probability in standard use cases, but significant risk in mintable tokens or high-inflation models)

  • Project owners or governance contracts may have unlimited minting privileges; without external validation, this issue is highly likely to be exploited

Impact:

  • _totalSupply or user balances become severely distorted due to wrapping, undermining the credibility of the token's economic model

  • Potential for logical attacks (e.g., using overflow to zero out or reduce balances, then profiting from other integrated mechanisms)

Proof of Concept

  • Add the function test_1_mint_overflow to Token.t.sol:

function test_1_mint_overflow() public {
uint256 amount = type(uint256).max;
address account = makeAddr("account");
token.mint(account, amount);
uint256 balance = token.balanceOf(account);
assertEq(balance, amount);
assertEq(token.totalSupply(), amount);
assertEq(token.totalSupply(), type(uint256).max);
// Mint 1 additional token
token.mint(account, 1);
assertEq(token.totalSupply(), 0); // Overflow causes wrap-around to 0
assertEq(token.balanceOf(account), 0); // Overflow causes wrap-around to 0
}
  • Execute command: forge test --mt test_1_mint_overflow -vv

Ran 1 test for test/Token.t.sol:TokenTest
[PASS] test_1_mint_overflow() (gas: 46828)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.00ms (385.20µs CPU time)

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)
+
+ // Check for total supply overflow
+ let newSupply := add(supply, value)
+ if lt(newSupply, supply) {
+ mstore(0x00, shl(224, 0x4e487b71))
+ mstore(add(0x00, 4), 0x11)
+ revert(0x00, 0x24)
+ }
sstore(supplySlot, add(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
+
+ // Check for account balance overflow
+ let newBalance := add(accountBalance, value)
+ if lt(newBalance, accountBalance) {
+ mstore(0x00, shl(224, 0x4e487b71))
+ mstore(add(0x00, 4), 0x11)
+ revert(0x00, 0x24)
+ }
sstore(accountBalanceSlot, add(accountBalance, value))
}
}
Updates

Lead Judging Commences

gaurangbrdv Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

overflow & underflow

overflow & underflow occurs

Support

FAQs

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

Give us feedback!