Reproduced Exploit
Omo: unrestricted onERC721Received locks 1000
depositPosition moves the NFT with transferFrom, so onERC721Received is not part of a real deposit. The hook still has no caller check. An attacker adds token id 2. topOffToAgent reverts on that id and the 1000 already deposited cannot leave.
Chain
Other
Category
dos
Date
Jan 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
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.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 53318-c-07-unrestricted-omoagentonerc721received-allows-permanent. Standalone Foundry PoC and full write-up: 53318-c-07-unrestricted-omoagentonerc721received-allows-permanent_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/dos/frozen-funds · vuln/access-control/missing-auth
Key info#
| Protocol | Omo |
| Finding | Pashov Audit Group C-07, AuditVault 53318 |
| Vulnerable code | OmoAgent.onERC721Received |
| Harm | A fake position id makes topOffToAgent revert, so 1000 of backing stays in the agent. The fixed hook does not track the id and pays the 1000. |
| Chain | local synthetic |
TL;DR#
depositPosition moves the NFT with transferFrom, so onERC721Received is not part of a real deposit. The hook still has no caller check. An attacker adds token id 2. topOffToAgent reverts on that id and the 1000 already deposited cannot leave.
The vulnerable code#
// @> VULN: any caller adds tokenId; depositPosition uses transferFrom so this hook never runs on a real deposit
_addPositionId(tokenId);
Root cause#
The hook writes _positionIds for any caller. A later loop requires every id to have a nonzero value.
Preconditions#
The agent already holds position 1 backed by 1000. The attacker can call the hook directly.
Attack walkthrough#
- Deposit position 1 with 1000 of backing. See output.txt.
- Call
onERC721Receivedwith token id 2. _addPositionIdstores id 2.positionCountis 2.topOffToAgentreverts on the unknown id.- The agent balance is still 1000. The fixed hook leaves the count at 1 and pays the 1000.
Diagrams#
Impact#
The owner's 1000 cannot be withdrawn. The fixed hook returns the selector and does not record the id, so topOff pays 1000.
Remediation#
Do not call _addPositionId from onERC721Received. The deposit path already records the id.
How to reproduce#
cd 53318-c-07-unrestricted-omoagentonerc721received-allows-permanent_exp
forge test -vv
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 53318-c-07-unrestricted-omoagentonerc721received-allows-permanent_exp (evm-hack-registry mirror).
- AuditVault finding: 53318-c-07-unrestricted-omoagentonerc721received-allows-permanent.
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.