Token-0x

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

Possible underflow detected in _burn function

Author Revealed upon completion

Root + Impact

Description

  • In function ERC20Internals::_burn there is a potential risk of underflow.

  • If the value of tokens burnt are higher than the user's balance. The final user's balance will be higher instead of lower as expected. Also, the total supply value of the contract could suffer underflow and get an incorrect 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:

  • Medium. There is a possibility in certain cases where the value burnt cause underflow. This can happen with the user's balance and with the total supply of the contract.

Impact:

  • High. Increasing the user's balance would cause a lost for the contract. Also having an incorrect value in total supply would be inconsistent.

Proof of Concept

Put below coding in Token.t.sol. It can be checked how the user's balance is highly increased (instead of decreasing).

function test_burn_underflow() public {
uint256 amount = 100e18;
address account = makeAddr("account");
token.mint(account, amount);
uint256 balanceBefore = token.balanceOf(account);
console.log("Balance before burn:", balanceBefore);
token.burn(account, amount + 50e18); // burn more than expected causing underflow
uint256 balanceAfter = token.balanceOf(account);
console.log("Balance after burn:", balanceAfter);
assert(balanceAfter > balanceBefore); // underflow happened
}

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)
+ if lt(supply, value) {
+ revert(0, 0)
+ }
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) {
+ revert(0, 0)
+ }
sstore(accountBalanceSlot, sub(accountBalance, value))
}
}

Support

FAQs

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

Give us feedback!