Token-0x

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

C-01: Critical - Unchecked Assembly Arithmetic (Panic 0x11)

Author Revealed upon completion

Root Cause

The _mint, _burn, and _transfer functions use raw EVM arithmetic opcodes (add, sub) within inline assembly without explicit overflow/underflow checks.

The counterexample for testFuzzSupplyConsistency shows the fuzzer injecting a value near type(uint256).max . When _mint executes sstore(supplySlot, add(supply, value)), the addition results in a wrapped (overflowed) value, which triggers an implicit revert (panic: arithmetic underflow or overflow (0x11)) inserted by the Solidity 0.8.x compiler.

Impact

A malicious user or unintended input can cause unrecoverable transaction failure (DoS) or, in certain contexts, silent state corruption if the EVM wraps the value before the compiler check executes. This breaks the core invariant that the total supply and individual balances are always positive and within uint256 limits.


Code Trace (Illustrative)

The revert occurs in ERC20Internals::_mint assembly:

// In _mint:
let supply := sload(supplySlot)
sstore(supplySlot, add(supply, value)) // <-- Panic occurs here

Vulnerability: Arithmetic Overflow/Underflow.


  • Proof: Failed test testFuzzSupplyConsistency with counterexample involving the maximum uint256 value, resulting in panic: arithmetic underflow or overflow (0x11).


  • : Denial of Service (DoS) on all core state-changing functions ($\text{mint, burn, transfer}$) if large values are used, or silent state corruption if the operation involves multiple assembly steps.

POC: - Test Suit for supplyConsistency function by stateful fuzzing

function testFuzzSupplyConsistency(address account, uint256 mintAmount, uint256 burnAmount) public {
// Constraints
vm.assume(account != ZERO_ADDRESS);
vm.assume(mintAmount > 0);
vm.assume(burnAmount < token.balanceOf(account) + mintAmount); // Prevent burn underflow
// 1. Initial State
uint256 initialSupply = token.totalSupply();
uint256 initialBalance = token.balanceOf(account);
// 2. Mint (Assembly _mint calls add on supply and balance)
token.doMint(account, mintAmount);
// 3. Burn (Assembly _burn calls sub on supply and balance)
// Check for underflow revert which is handled by assembly's sub opcode (Solidity 0.8+ context)
if (initialBalance + mintAmount < burnAmount) {
vm.expectRevert();
token.doBurn(account, burnAmount);
} else {
token.doBurn(account, burnAmount);
// Final Check
uint256 expectedSupply = initialSupply + mintAmount - burnAmount;
uint256 expectedBalance = initialBalance + mintAmount - burnAmount;
assertEq(token.totalSupply(), expectedSupply, "Final total supply is inconsistent");
assertEq(token.balanceOf(account), expectedBalance, "Final balance is inconsistent");
}
}

Mitigation

Implement manual safe arithmetic checks in assembly before every add or sub operation.

// Mitigation Example for ADD (Overflow Check)
assembly {
let newSupply := add(supply, value)
// If the new value is less than the current supply, overflow occurred.
if lt(newSupply, supply) {
// Revert with a specific error (ERC20InsufficientBalance or custom)
revert(0, 0) // Placeholder
}
sstore(supplySlot, newSupply)
}

Support

FAQs

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

Give us feedback!