function _transfer(address from, address to, uint256 value) internal returns (bool success) {
assembly ("memory-safe") {
if iszero(from) {
mstore(0x00, shl(224, 0x96c6fd1e))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
if iszero(to) {
mstore(0x00, shl(224, 0xec442f05))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let baseSlot := _balances.slot
mstore(ptr, from)
mstore(add(ptr, 0x20), baseSlot)
let fromSlot := keccak256(ptr, 0x40)
let fromAmount := sload(fromSlot)
mstore(ptr, to)
mstore(add(ptr, 0x20), baseSlot)
let toSlot := keccak256(ptr, 0x40)
let toAmount := sload(toSlot)
if lt(fromAmount, value) {
mstore(0x00, shl(224, 0xe450d38c))
mstore(add(0x00, 4), from)
mstore(add(0x00, 0x24), fromAmount)
mstore(add(0x00, 0x44), value)
revert(0x00, 0x64)
}
sstore(fromSlot, sub(fromAmount, value))
sstore(toSlot, add(toAmount, value))
success := 1
mstore(ptr, value)
log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, from, to)
}
}
There are many ways to overflow it,especially with this contract,The mint and burn promote overflow and underflow,and can be used to bring any users account to 0.
function test_transfer_overflow() public {
address account = makeAddr("account");
token.mint(account, 100e18);
uint256 balanceSender = token.balanceOf(account);
assertEq(balanceSender, 100e18);
address receiver = makeAddr("receiver");
token.mint(receiver, type(uint).max);
vm.prank(account);
token.transfer(receiver, 1);
balanceSender = token.balanceOf(account);
uint256 balanceReceiver = token.balanceOf(receiver);
assertEq(balanceReceiver, 0);
}
+ add this code
// fromSlot update (unchanged)
sstore(fromSlot, sub(fromAmount, value))
// toSlot update with overflow guard
let newToAmount := add(toAmount, value)
if lt(newToAmount, toAmount) {
// overflow happened
// You can use any selector you prefer
mstore(0x00, shl(224, 0x8b3e16f1)) // Overflow(uint256,address)
revert(0x00, 0x04)
}