Token-0x

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

Unchecked `_mint` Can Overflow `totalSupply` and Account Balances

Description:
The internal _mint implementation in ERC20Internals performs unchecked arithmetic operations on both totalSupply and the account balance, without proper overflow validation:

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

There are no checks to ensure that totalSupply + value and balanceOf(account) + value do not overflow. It can lead to:

  • Wrap totalSupply back to zero or a small number.

  • Wrap a holder’s balance back to zero or a smaller number, essentially “burning” their tokens without any revert.

All of this happens silently, which is very dangerous for a library that aims to be “secure and cheap” and is intended as a base for DeFi protocols.

Impact:

  • If the contract mints very large amounts or is used in a context where totalSupply can approach type(uint256).max, it will overflow and wrap to a small number without reverting.

  • This breaks core invariants expected by other contracts (e.g., sum(balances) == totalSupply).

  • In extreme scenarios, user balances can wrap to zero after minting (a permanent loss of effective tokens from the holder’s perspective).

This is a high‑risk correctness bug. While it may seem unlikely to hit 2^256 in practice, protocols often reason symbolically about totalSupply, so the lack of any safety guard is not acceptable in a reusable base contract.

Proof of Concept:

Token using Token-0x ERC20:

// Add this test to Token.t.sol
function test_mintOverflowWrapsSupplyAndBalance() public {
address alice = makeAddr("alice");
// First mint: set balance and totalSupply to max uint256
token.mint(alice, type(uint256).max);
assertEq(token.totalSupply(), type(uint256).max);
assertEq(token.balanceOf(alice), type(uint256).max);
// Second mint of 1 token will overflow both totalSupply and balance
token.mint(alice, 1);
// Now both should have wrapped around (max + 1 == 0 mod 2^256)
assertEq(token.totalSupply(), 0, "totalSupply has wrapped to 0");
assertEq(token.balanceOf(alice), 0, "balance has wrapped to 0");
}

Token2 using OpenZeppelin ERC20:

// Add this test to Token2.t.sol
function test_mintOverflowReverts_OZ() public {
address alice = makeAddr("alice");
// First mint to max uint256
token.mint(alice, type(uint256).max);
assertEq(token.totalSupply(), type(uint256).max);
// Second mint of 1 in OZ should revert due to checked arithmetic
vm.expectRevert();
token.mint(alice, 1);
}

OpenZeppelin’s implementation reverts when totalSupply + amount overflows, while Token-0x silently wraps, proving the issue.

Mitigation:

  • Add overflow checks in _mint for both totalSupply and the recipient’s balance:

function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0xec442f05))
mstore(add(0x00, 4), account)
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) {
+ // Revert with overflow error
+ revert(0, 0)
+ }
+ sstore(supplySlot, newSupply)
- sstore(supplySlot, add(supply, value))
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, newBalance)
- sstore(accountBalanceSlot, add(accountBalance, value))
}
}
  • Implement mint logic in Solidity with default overflow checks and only optimize storage access with assembly.

Updates

Lead Judging Commences

gaurangbrdv Lead Judge about 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

overflow & underflow

missing checks for overflow and underflow.

Support

FAQs

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

Give us feedback!