Token-0x

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

Missing check to avoid possible overflow

Author Revealed upon completion

Root + Impact

Description

  • There is a missing check to avoid possible overflow.

  • Below are described all the places where the overflow could be raised:

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)
}
}
function _mint(address account, uint256 value) internal {
assembly ("memory-safe") {
if iszero(account) {
// written. Shouldn't use memory 0x00 to save data.
mstore(0x00, shl(224, 0xec442f05))
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, add(supply, value))
mstore(ptr, account)
mstore(add(ptr, 0x20), balanceSlot)
let accountBalanceSlot := keccak256(ptr, 0x40)
let accountBalance := sload(accountBalanceSlot)
@> sstore(accountBalanceSlot, add(accountBalance, value))
}
}

Risk

Likelihood:

  • Medium. A malicious user could exploit this vulnerability. For ex, could force a transfer to make overflow the balance of a user. Although this scenario is difficult given that the malicious user should spend tokens, is possible. Or maybe there is no malicious user, and just happens because of the dynamic of the contract.

Impact:

  • High. The balance of a user could pass from being high to zero or low.

Proof of Concept

Put below code in Token.t.sol:

//>>audit - 09.12.25
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(uint256).max);
uint256 balanceReceiverBefore = token.balanceOf(receiver);
console.log("Balance receiver before:", balanceReceiverBefore);
vm.prank(account);
token.transfer(receiver, 50e18);
uint256 balanceReceiverAfter = token.balanceOf(receiver);
console.log("Balance receiver after:", balanceReceiverAfter);
assert(balanceReceiverAfter < balanceReceiverBefore); // overflow happened
}
//<<audit - 09.12.25

Recommended Mitigation

A check must be put in place to avoid overflow occurs:

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))
+// @audit. Below check could be set to avoid possible overflow
+ if lt(sub(type(uint256).max, value), toAmount) {
+ revert(...)
+ }
sstore(toSlot, add(toAmount, value))
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!