Token-0x

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

`ERC20Internals::_transfer` causes overflow in recepient's account balance and leads to loss of recepient's funds

Author Revealed upon completion

Impact:H

Likelihood:H

Root + Impact

Description

  • Recipient's account balance is not correctly calculated when the balance overflows, showing an incorrect value.

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:

  • The issue occurs when transfer amount of tokens that lead to arithmetic overflow.

Impact:

  • Recipient's balance is incorrectly calculated, leading to loss of recipient's funds because after overflow the recipient's balance is less than it was previously.

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);
//to cause overflow it will be enough to send 2 wei
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);
// the receiver's balance after the transfer is less than the balance before the transfer
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)
}
}

Support

FAQs

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

Give us feedback!