Reproduced Exploit

HandleTokenMintAndSwap decodes a 1,000 token mint and credits 0

HandleTokenMintAndSwap abi-decodes the spotter payload, then decodes TokenMintAndSwap into a recipient and an amount. The body is the TODO handle? return from the Halborn quote. A payload that says mint 1,000 tokens leaves the user at 0. The control handler mints those 1,000 tokens to the user.

Jun 2024•Ethereum•logic•3 min read

Loss

A TokenMintAndSwap for 1,000 tokens is decoded and the user receives 0. Control mints 1,000

Chain

Ethereum

Category

logic

Date

Jun 2024

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 51373-incomplete-implementation-of-handletokenmintandswap-in-maste. Standalone Foundry PoC and full write-up: 51373-incomplete-implementation-of-handletokenmintandswap-in-maste_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/logic/state-update

Reproduction: self-contained synthetic Foundry reduction; see output.txt.

Key info#

FieldValue
LossA TokenMintAndSwap for 1,000 tokens is decoded and the user receives 0. Control mints 1,000
Vulnerable contractMasterSpotter.HandleTokenMintAndSwap
Attacker EOA0x1111111111111111111111111111111111111111
Attack contractExploit
Attack txExploit.run()
Chain / block / dateEthereum model · block 0 · 2024-06
Compilersolc 0.8.27, cancun, via IR
Bug classThe handler decodes the event and returns without minting

TL;DR#

HandleTokenMintAndSwap abi-decodes the spotter payload, then decodes TokenMintAndSwap into a recipient and an amount. The body is the TODO handle? return from the Halborn quote. A payload that says mint 1,000 tokens leaves the user at 0. The control handler mints those 1,000 tokens to the user.

Background#

Halborn reviewed Entangle Trillion. MasterSpotter.HandleTokenMintAndSwap is the spotter entry for a token mint-and-swap event. The quoted function decodes (bytes32, uint256, uint256, bytes32, bytes) and then DecodeTokenMintAndSwap, and returns. The report says the event is not processed. This reduction keeps that decode and treats the inner payload as (recipient, amount), which is the mint the function name is supposed to perform.

The vulnerable code#

SOLIDITY
function HandleTokenMintAndSwap(bytes calldata b) external returns (uint256 credited) {
    (,,,, bytes memory eventParams) = abi.decode(b, (bytes32, uint256, uint256, bytes32, bytes));
    (address recipient, uint256 amount) = abi.decode(eventParams, (address, uint256));
    seenUser = recipient;
    seenAmount = amount;
    // @> VULN: TokenMintAndSwap is decoded and then dropped
    credited = 0;
}

The control function assigns credited = amount and mints that amount to recipient.

Root cause#

Decoding the event is not handling it. seenAmount proves the 1,000 was read. The next write is credited = 0, and the token is never minted, so the user balance stays 0.

Preconditions#

  • The payload is the quoted outer tuple plus an inner (recipient, 1000 ether) mint.
  • The handler is invoked in this transaction. No other role check is required to show the drop. The original function is onlyRole(SPOTTER); the spotter is the intended caller.
  • The control handler receives the same payload.

Attack walkthrough#

  1. The buggy handler returns credited 0 (output.txt:9). The user balance stays 0 (output.txt:11).
  2. The control handler mints 1000000000000000000000 (output.txt:13).
  3. [PASS] (output.txt:6).

Diagrams#

flowchart TD Event["Payload says mint 1000 to the user"] --> Decode["Handler decodes recipient and amount"] Decode --> Drop["credited is written as 0"] Drop --> Zero["User balance stays 0"] Zero --> Control["Control mints the 1000"]

Impact#

The user named in the event does not receive the 1,000 tokens the payload described. The handler still returns success with credited 0. The completed handler mints 1,000 to that user.

Remediation#

After DecodeTokenMintAndSwap, mint amount to the recipient and perform the swap the event names. Do not return on the TODO.

DIFF
-    credited = 0;
+    credited = amount;
+    token.mint(recipient, credited);

How to reproduce#

BASH
cd 51373-incomplete-implementation-of-handletokenmintandswap-in-maste_exp
forge test -vv

Sources#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.