Reproduced Exploit
transferPerpOwner can be front-run, then the new perp pulls the reserves
transferPerpOwner sets perp on the first call. Anyone can be that caller. Eve calls it with her own address. The admin's call then reverts OwnershipAlreadyClaimed. Eve is the only address allowed to pull the virtual-token reserves, and she pulls 1,000 UA to the attacker. A two-step transfer ignores…
Loss
1,000 UA pulled by the front-runner
Chain
Ethereum
Category
access-control
Date
Sep 2022
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: 17365-ownership-transfers-can-be-front-run-trailofbits-increment-f. Standalone Foundry PoC and full write-up: 17365-ownership-transfers-can-be-front-run-trailofbits-increment-f_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/access-control/missing-auth
Key info#
| Field | Value |
|---|---|
| Loss | 1,000 UA pulled by the front-runner |
| Vulnerable contract | PerpOwnable.transferPerpOwner |
| Attacker EOA | 0x1111111111111111111111111111111111111111 |
| Attack contract | Exploit |
| Attack tx | Exploit.run() |
| Chain / block / date | Ethereum model · block 0 · 2022-09 |
| Compiler | solc 0.8.27, cancun, via IR |
| Bug class | First caller of an unauthenticated transfer becomes the privileged owner |
TL;DR#
transferPerpOwner sets perp on the first call. Anyone can be that caller. Eve calls it with her own address. The admin's call then reverts OwnershipAlreadyClaimed. Eve is the only address allowed to pull the virtual-token reserves, and she pulls 1,000 UA to the attacker. A two-step transfer ignores Eve: she cannot nominate and she cannot accept. The admin nominates the market, the market accepts, and the market pulls the same 1,000 UA to the treasury.
The vulnerable code#
function transferPerpOwner(address recipient) external virtual {
if (recipient == address(0)) revert PerpOwnable_TransferZeroAddress();
// @> VULN: the first caller sets perp. No role and no accept step.
if (perp != address(0)) revert PerpOwnable_OwnershipAlreadyClaimed();
perp = recipient;
emit PerpOwnerTransferred(msg.sender, recipient);
}
pull then pays the reserves, and it requires msg.sender == perp.
Root cause#
The function is external and the only state check is that perp is still unset. The caller is not required to be the admin, the market, or a pre-committed nominee. The first transaction in the mempool wins the role, and every later claim reverts.
Preconditions#
perpis unset. That is the deployment state the review describes, before the admin points the virtual token at the perpetual market.- The contract holds 1,000 UA.
pullsends that balance and is gated byperp.
Attack walkthrough#
- 1,000 UA is minted into
PerpOwnable(output.txt).perpis address zero. - Eve calls
transferPerpOwner(eve). The unset check does not revert.perpbecomes Eve. The transfer event records her. - The admin calls
transferPerpOwner(treasury). It revertsOwnershipAlreadyClaimed. - Eve calls
pull(attacker, 1000 ether). Themsg.sender == perpcheck passes. The attacker balance is 1,000 UA and the contract balance is 0. - Control: Eve's
transferPerpOwnerreverts because she is not the admin, and heracceptPerpOwnerreverts because she was not nominated. Herpullreverts. The admin nominates the market. The market accepts and pulls the 1,000 UA to the treasury. The attacker's balance stays 0.
Diagrams#
Impact#
The attacker receives 1,000 UA (1000000000000000000000 base units). The admin never becomes perp. On the two-step contract Eve cannot complete the transfer, and the 1,000 UA is pulled by the nominated market to the treasury instead.
Remediation#
Replace the one-shot assignment with a two-step transfer. Only the admin may nominate. Only the nominee may accept, and only that acceptance writes perp.
How to reproduce#
cd evm-hack-registry/17365-ownership-transfers-can-be-front-run-trailofbits-increment-f_exp
forge test -vv
Sources#
- AuditVault finding 17365
- Trail of Bits, Increment Protocol review, September 2022 (
contracts/utils/PerpOwnable.sol) - Synthetic
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 17365-ownership-transfers-can-be-front-run-trailofbits-increment-f_exp (evm-hack-registry mirror).
- AuditVault finding: 17365-ownership-transfers-can-be-front-run-trailofbits-increment-f.
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.