Reproduced Exploit

Revolution / Collective — art-piece quorum inflated by auctioned ERC721 voting power

1. createPiece sets totalVotesSupply from erc20.totalSupply() + weighted erc721.totalSupply(). 2. The ERC721 currently held by AuctionHouse is included but cannot vote for that piece. 3. quorumVotes is computed from the inflated supply. 4. HARM: with 1000e18 accessible and weight-100 NFT on auction…

Dec 2023Othergovernance3 min read

Chain

Other

Category

governance

Date

Dec 2023

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: 30089-h-02-artpiecetotalvotessupply-and-artpiecequorumvotes-are-in. Standalone Foundry PoC and full write-up: 30089-h-02-artpiecetotalvotessupply-and-artpiecequorumvotes-are-in_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/governance/quorum-manipulation · vuln/arithmetic/precision-loss · genome: precision-loss · locked-funds · quorum-supply-drift

Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only forge-std — no fork, no RPC, no anvil_state. Full trace: output.txt. PoC: test/30089-h-02-artpiecetotalvotessupply-and-artpiecequorumvotes-are-in_exp.sol.

AuditVault taxonomy: lang/solidity · platform/code4rena · has/github · has/poc · severity/high · sector/governance · sector/nft · sector/token · genome: precision-loss · locked-funds · quorum-supply-drift


Key info#

ImpactHIGH — quorum for new art pieces is inflated by inaccessible auction NFT weight
ProtocolRevolution Protocol / Collective
Vulnerable codeCultureIndex.createPiecetotalVotesSupply / quorumVotes snapshot
Bug classQuorum supply drift / inaccessible voting power counted
FindingCode4rena 2023-12-revolutionprotocol · H-02 · #30089
Reportcode4rena.com/reports/2023-12-revolutionprotocol
SourceAuditVault
StatusConfirmed (sponsor). Reproduced as standalone local PoC.
Compiler^0.8.24 (PoC)

TL;DR#

  1. createPiece sets totalVotesSupply from erc20.totalSupply() + weighted erc721.totalSupply().
  2. The ERC721 currently held by AuctionHouse is included but cannot vote for that piece.
  3. quorumVotes is computed from the inflated supply.
  4. HARM: with 1000e18 accessible and weight-100 NFT on auction, honest 50% (500e18) fails the inflated 550e18 quorum (effective 55%).

The vulnerable code#

SOLIDITY
newPiece.totalVotesSupply = _calculateVoteWeight(
    erc20VotingToken.totalSupply(),
    erc721VotingToken.totalSupply() // @> VULN: includes ERC721 on auction
);
newPiece.quorumVotes = (quorumVotesBPS * newPiece.totalVotesSupply) / 10_000; // @> VULN: inflated

Fix: subtract erc721VotingToken.balanceOf(auctionHouse) when snapshotting ERC721 supply.


Root cause#

Vote-supply snapshot ignores that the live auction NFT’s voting power is inaccessible: the auction house does not vote, and the eventual buyer is checkpoint-gated against pieces created before they held the token.


Preconditions#

  • An ERC721 verbs token is mid-auction (balance sits in AuctionHouse) when a new piece is created.
  • Impact is largest early (small ERC721 supply, high weight).

Attack walkthrough#

  1. State: 1000e18 erc20, 1 NFT in AuctionHouse, weight=100, quorum BPS=5000.
  2. createPiecetotalVotesSupply=1100e18, quorumVotes=550e18.
  3. Accessible supply is still 1000e18.
  4. Cast honest half (500e18) → fails inflated quorum.

Diagrams#

flowchart TD A["createPiece while NFT on auction"] --> B["totalSupply ERC721 includes auction NFT"] B --> C["totalVotesSupply = 1100e18"] C --> D["quorumVotes = 550e18"] E["accessible supply = 1000e18"] --> F["honest 50% = 500e18"] F --> G{"500e18 >= 550e18?"} G -->|"no"| H["quorum not reached"]

Impact#

Pieces created during an auction face a higher-than-configured quorum relative to votes that can actually be cast — unfair vs pieces created when no NFT is locked, and harder to advance to auction especially at launch.


How to reproduce#

BASH
cd evm-hack-registry/30089-h-02-artpiecetotalvotessupply-and-artpiecequorumvotes-are-in_exp
forge test -vvv

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.