Reproduced Exploit

GemPadLock reentrancy (Dec 2024) — multi-chain ~$2M

1. collectFees(lockId) snapshots GemPad balances, calls INonfungiblePositionManager.collect() on a UniV3 NFT lock, then refunds balance deltas to the lock owner. 2. collect() eventually transfers pool tokens. If the fee token is attacker-controlled, transfer re-enters multipleLock. 3. multipleLock…

Dec 2024Otheruntagged3 min read

Loss

~$1.9–2.2M locked LP / ERC20 inventory

Chain

Other

Category

untagged

Date

Dec 2024

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, 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. Exploit reproduction, trace data, and analysis adapted from DeFiHackLabs by SunWeb3Sec — an open registry of reproduced on-chain exploits. Standalone Foundry PoC and full write-up: 2024-12-GemPadLock_exp in the evm-hack-registry mirror. Upstream DeFiHackLabs PoC: src/test/…/GemPadLock_exp.sol.


Summary#

FieldValue
Date2024-12-17
ChainsEthereum, BSC, Base (same lock proxy family)
Loss~$1.9–2.2M locked LP / ERC20 inventory
Bug classMissing nonReentrant + balance-delta fee collect + re-enter multipleLock via malicious ERC20
Proxy0x10B5F02956d242aB770605D59B7D27E51E45774C
Pre-fix impl0xc25c516eb7d86b5ec38c07182f4a2f73ac81eead
Post-fixnonReentrant on public lock/fee paths (impl upgraded)

Root cause#

  1. collectFees(lockId) snapshots GemPad balances, calls INonfungiblePositionManager.collect() on a UniV3 NFT lock, then refunds balance deltas to the lock owner.
  2. collect() eventually transfers pool tokens. If the fee token is attacker-controlled, transfer re-enters multipleLock.
  3. multipleLock pulls attacker tokens into GemPad and registers a new lockId while still inside the fee-collect balance window.
  4. The balance delta after re-entry is refunded to the attacker; the free lockId later unlocks victim inventory (this PoC: FOMO–WETH UniV2 LP).

Call chain:

TEXT
GemPadLock.collectFees
  → NPM.collect → UniV3Pool.collect
    → MaliciousToken.transfer
      → GemPadLock.multipleLock  // free lock over victim ERC20/LP

Addresses (Ethereum PoC path)#

RoleAddress
Attacker EOA0xFDd9b0A7e7e16b5Fd48a3D1e242aF362bC81bCaa
Attack contract (historical)0x8e18Fb32061600A82225CAbD7fecF5b1be477c43
GemPadLock proxy0x10B5F02956d242aB770605D59B7D27E51E45774C
Victim LP exampleFOMO 0x9028C2A7f8C8530450549915c5338841Db2a5fEa / WETH UniV2 pair
BSC attack tx0x5c21611ae260cf795c304cf9fcf777450acf29916d162964b7ec61c37632a07e
ETH attack tx0x7b67e39cd253724372d67da78221a38eca98d2a6b69027a89010bca2101dd02a

PoC#

FieldValue
Folder2024-12-GemPadLock_exp
Testtest/GemPadLock_exp.sol
ForkEthereum block 21420584 (pre-fix / Decurity educational block)
RPC usedhttps://eth.drpc.org (archive)
Offline anvil_statenot shipped (ONLINE archive)

Run#

BASH
unset ETH_RPC_URL FOUNDRY_ETH_RPC_URL   # avoid non-archive override
cd 2024-12-GemPadLock_exp
forge test --match-contract GemPadLock_exp -vv --gas-limit 100000000

Expected#

  • free lockIds minted via reentrancy: > 0 (PoC logs 10)
  • Attacker ETH balance increases after unlock + removeLiquidity (observed ~+44 ETH in this run)

References#

Notes#

  • Fixed live code has nonReentrant; fork must be pre-upgrade.
  • Teaching PoC reimplements Decurity’s malicious-token + UniV3 fee path (not byte-identical to attacker bytecode).
  • Multi-chain: same proxy address pattern on BSC/Base; this folder ships ETH FOMO-LP path only.

Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

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.