Reproduced Exploit
Foundation — Creators can steal sale revenue from owners' sales
1. Secondary sales should pay ~10% royalty to creators and the rest to the seller. 2. If getRoyalties lists the seller as a recipient, the market treats the sale as a creator sale (isCreator=true). 3. Creator-sale branch sets creatorRev = price - fee and leaves ownerRev = 0. 4. Creator updates the…
Chain
Other
Category
untagged
Date
Feb 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: 42485-h-02-creators-can-steal-sale-revenue-from-owners-sales-code4. Standalone Foundry PoC and full write-up: 42485-h-02-creators-can-steal-sale-revenue-from-owners-sales-code4_exp in the
evm-hack-registrymirror.
Vulnerability classes: impact/loss-of-funds/direct-drain · genome/royalty-edge-cases · spot-price
Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only
forge-std— no fork, no RPC, noanvil_state. Full trace: output.txt. PoC: test/42485-h-02-creators-can-steal-sale-revenue-from-owners-sales-code4_exp.sol.
AuditVault taxonomy: lang/solidity · platform/code4rena · has/github · has/poc · severity/high · sector/nft · sector/nft-marketplace · genome: spot-price · direct-drain · integer-bounds · oracle-manipulation-resistance · royalty-edge-cases
Key info#
| Impact | HIGH — on a secondary sale, if the seller is listed as a royalty recipient, isCreator=true and ownerRev=0; creator takes price − fee |
| Protocol | Foundation — NFTMarketCreators / NFTMarketFees |
| Vulnerable code | _getCreatorPaymentInfo seller-in-recipients short-circuit; _getFees creator-sale branch |
| Bug class | Incorrect creator detection via mutable royalty registry |
| Finding | Code4rena — Foundation, 2022-02 · #42485 (H-02) · reporter jmak / IllIllI |
| Report | 2022-02-foundation |
| Source | AuditVault |
| Status | Audit finding — confirmed by Foundation; fixed by decoupling isCreator from royalty overrides. |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
- Secondary sales should pay ~10% royalty to creators and the rest to the seller.
- If
getRoyaltieslists the seller as a recipient, the market treats the sale as a creator sale (isCreator=true). - Creator-sale branch sets
creatorRev = price - feeand leavesownerRev = 0. - Creator updates the Royalty Registry right before settlement.
- HARM in the PoC: 10 ETH sale → seller gets 0, creator receives 9.5 ETH (after 5% secondary fee).
The vulnerable code#
From code-423n4/2022-02-foundation NFTMarketCreators.sol / NFTMarketFees.sol:
if (_recipients[i] == seller) {
return (_recipients, recipientBasisPoints, true); // @> VULN: isCreator
}
if (isCreator) {
creatorRev = price - foundationFee; // @> VULN: ownerRev stays 0
} else {
creatorRev = (price * CREATOR_ROYALTY_BASIS_POINTS) / BASIS_POINTS;
ownerRevTo = seller;
ownerRev = price - foundationFee - creatorRev;
}
Fix (shipped): determine isCreator via tokenCreator (or similar), not royalty-recipient membership; always compute owner revenue separately from royalties.
Root cause#
isCreator was overloaded to mean both "seller is the creator" and "seller appears in royalty recipients" (needed for split primary sales). The Royalty Registry makes recipient lists mutable post-mint, so a creator can flip a secondary sale into the creator-sale payout path on demand.
Preconditions#
- NFT has mutable royalty info (Royalty Registry /
IGetRoyalties). - Secondary sale of a token the creator does not own.
- Creator (or bribed party) can update recipients before settlement.
Attack walkthrough#
- Baseline secondary split: fee 5%, royalty 10%, owner 85%.
- Creator sets royalties to include the seller address.
_getCreatorPaymentInforeturnsisCreator=true._getFeesassigns all post-fee revenue to creator; ownerRev=0.- Buyer pays 10 ETH; creator receives 9.5 ETH; seller receives 0.
Diagrams#
Remediation#
- (creatorRecipients, creatorShares, isCreator) = _getCreatorPaymentInfo(..., seller);
+ bool isCreator = (tokenCreator(nft, tokenId) == seller);
+ (creatorRecipients, creatorShares) = _getCreatorPaymentInfo(nft, tokenId); // no seller short-circuit
How to reproduce#
cd ~/RustroverProjects/audits/evm-hack-registry/42485-h-02-creators-can-steal-sale-revenue-from-owners-sales-code4_exp
forge test -vvv
# Fully local -- payable run() supplies the 10 ETH sale.
Sources#
- AuditVault finding: 42485-h-02-creators-can-steal-sale-revenue-from-owners-sales-code4.md
- Code4rena report: 2022-02-foundation
- Vulnerable repo: code-423n4/2022-02-foundation@a03a7e1
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 42485-h-02-creators-can-steal-sale-revenue-from-owners-sales-code4_exp (evm-hack-registry mirror).
- AuditVault finding: 42485-h-02-creators-can-steal-sale-revenue-from-owners-sales-code4.
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.