Token-0x

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

`_burn` allows underflow

Author Revealed upon completion

Root + Impact

Description

  • Burning tokens should verify that the account has enough balance before reducing it and the total supply reverting if the balance is insufficient

  • The function does unchecked subtraction in assembly without validating that accountBalance >= value

function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0x96c6fd1e))
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, sub(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
@> sstore(accountBalanceSlot, sub(accountBalance, value))
}
}

Risk

Likelihood:

  • Many token implementations expose burn functionality to users for deflationary mechanics

  • Reason 2

Impact:

  • Attacker can drain all liquidity pools


Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "./ERC20.sol";
contract BurnableToken is ERC20 {
constructor() ERC20("Burnable", "BURN") {
_mint(msg.sender, 1000e18);
}
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
function testBurnUnderflow() external {
address attacker = address(0x1234);
uint256 initialBalance = this.balanceOf(attacker);
this.burn(1);
uint256 finalBalance = this.balanceOf(attacker);
assert(finalBalance > 1e70);
}
}

Recommended Mitigation

function _burn(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
mstore(0x00, shl(224, 0x96c6fd1e))
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, sub(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
+
+ if lt(accountBalance, value) {
+ mstore(0x00, shl(224, 0xe450d38c))
+ mstore(add(0x00, 4), account)
+ mstore(add(0x00, 0x24), accountBalance)
+ mstore(add(0x00, 0x44), value)
+ revert(0x00, 0x64)
+ }
+
+ if lt(supply, value) {
+ revert(0, 0)
+ }
+
+ sstore(supplySlot, sub(supply, value))
sstore(accountBalanceSlot, sub(accountBalance, value))
}
}

Support

FAQs

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

Give us feedback!