Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
View results
Submission Details
Severity: high
Valid

Token inflation

Root + Impact

Description

  • When transferring tokens the function should reduce the amount from the senders balance and add it to the receiverss balance

  • When a user transfers tokens to themselves the function reads the balance into toAmount before updating fromSlot. Since both slots are identical when from == to the final write to toSlot overwrites the deduction with toAmount + value

function _transfer(address from, address to, uint256 value) internal returns (bool success) {
assembly ("memory-safe") {
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) {
}
@> sstore(fromSlot, sub(fromAmount, value)) )
@> sstore(toSlot, add(toAmount, value))
success := 1
mstore(ptr, value)
log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, from, to)
}
}

Risk

Likelihood:

  • Any user can do this

  • No priviledge access required

Impact:

  • An attacker can mint unlimited tokens by self transferring

  • The destruction of token economics

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC20} from "./ERC20.sol";
contract TestToken is ERC20 {
constructor() ERC20("Test", "TST") {
_mint(msg.sender, 1000e18);
}
}
contract ExploitTest {
function testSelfTransferInflation() public {
TestToken token = new TestToken();
uint256 initialBalance = token.balanceOf(address(this));
token.transfer(address(this), 100e18);
uint256 finalBalance = token.balanceOf(address(this));
assert(finalBalance == initialBalance + 100e18);
}
}

Recommended Mitigation

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)
}
+
+ if eq(from, to) {
+ success := 1
+ mstore(0x00, value)
+ log3(0x00, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, from, to)
+ leave
+ }
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)
}
}
Updates

Lead Judging Commences

gaurangbrdv Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

transfer outstanding

transfer related exploit that can make huge impact to protocol.

Support

FAQs

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

Give us feedback!