Impact:H
Likelihood:H
Root + Impact
Description
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)
}
}
Risk
Likelihood:
Impact:
Proof of Concept
Please, insert the following test to Token.t.sol:
function test_transfer_overflow() public {
address sender = makeAddr("sender");
address receiver = makeAddr("receiver");
uint256 amountToMint = type(uint256).max - 1;
deal(address(token), sender, amountToMint);
deal(address(token), receiver, amountToMint);
uint256 balanceSenderBefore = token.balanceOf(sender);
assertEq(balanceSenderBefore, amountToMint);
uint256 balanceReceiverBefore = token.balanceOf(receiver);
assertEq(balanceReceiverBefore, amountToMint);
uint256 amountToSend = 2;
vm.prank(sender);
vm.expectEmit(true, true, false, true);
emit Transfer(sender, receiver, amountToSend);
token.transfer(receiver, amountToSend);
uint256 balanceSenderAfter = token.balanceOf(sender);
uint256 balanceReceiverAfter = token.balanceOf(receiver);
assertEq(balanceSenderAfter, balanceSenderBefore - amountToSend);
assertEq(balanceReceiverAfter, 0);
assertGt(balanceReceiverBefore, balanceReceiverAfter);
}
Recommended Mitigation
Yul does not natively checks on overflow, so the check should be added:
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))
+ let result := add(toAmount, value)
+ if lt(result, toAmount) {
+ revert(0, 0) // Revert if overflow occurred
+ }
+ sstore(toSlot, result)
success := 1
mstore(ptr, value)
log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, from, to)
}
}