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))
}
}
pragma solidity ^0.8.24;
import {Test} from "forge-std/Test.sol";
import {ERC20} from "../src/ERC20.sol";
contract BurnHarness is ERC20 {
constructor() ERC20("Token", "TKN") {}
function exposedBurn(address account, uint256 value) external {
_burn(account, value);
}
}
contract BurnUnderflow is Test {
BurnHarness internal token;
address internal attacker = address(0x2028);
function setUp() public {
token = new BurnHarness();
}
function test_burnUnderflow() public {
uint256 amount = 1 ether;
token.exposedBurn(attacker, amount);
uint256 expected = type(uint256).max - amount + 1;
assertEq(token.balanceOf(attacker), expected, "balance underflowed to max");
assertEq(token.totalSupply(), expected, "total supply underflowed");
}
}
//before subtracting first verify value <=accountBalance and value <= supply
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))
+ if lt(supply, value) { //check for underflow
+ mstore(0x00, shl(224, 0xe450d38c))
+ mstore(add(0x00, 4), account)
+ mstore(add(0x00, 0x24), supply)
+ mstore(add(0x00, 0x44), value)
+ revert(0x00, 0x64)
+ }
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
- sstore(accountBalanceSlot, sub(accountBalance, value))
+ if lt(accountBalance, value) { //check for underflow
+ mstore(0x00, shl(224, 0xe450d38c))
+ mstore(add(0x00, 4), account)
+ mstore(add(0x00, 0x24), accountBalance)
+ mstore(add(0x00, 0x44), value)
+ revert(0x00, 0x64)
+ }
+
+ sstore(accountBalanceSlot, sub(accountBalance, value))
+ sstore(supplySlot, sub(supply, value))
+
+ mstore(ptr, value)
+ log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, account, 0)
}
}