Token-0x

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

`_mint` can overflow

Author Revealed upon completion

Root + Impact

Description

  • Minting tokens should increase total supply and account balance with overflow protection

  • The function does unchecked addition without validating overflow

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)
@> 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:

  • Contracts with unrestricted minting or large mint caps are vulnerable

  • Reason 2

Impact:

  • Account balances overflow creating inconsistent state

  • Protocol relying on total supply for calculations produces incorrect results

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "./ERC20.sol";
contract MintableToken is ERC20 {
constructor() ERC20("Mintable", "MINT") {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function testMintOverflow() external {
uint256 maxUint = type(uint256).max;
address user = address(0x1234);
this.mint(user, maxUint - 100);
uint256 supplyAfterFirst = this.totalSupply();
this.mint(user, 200);
uint256 supplyAfterSecond = this.totalSupply();
assert(supplyAfterSecond == 99);
assert(supplyAfterSecond < supplyAfterFirst);
}
}

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)
- sstore(supplySlot, add(supply, value))
+ let newSupply := add(supply, value)
+ if lt(newSupply, supply) {
+ revert(0, 0)
+ }
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
- sstore(accountBalanceSlot, add(accountBalance, value))
+ let newBalance := add(accountBalance, value)
+ if lt(newBalance, accountBalance) {
+ revert(0, 0)
+ }
+
+ sstore(supplySlot, newSupply)
+ sstore(accountBalanceSlot, newBalance)
}
}

Support

FAQs

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

Give us feedback!