Reproduced Exploit

UMA prepaid proposer reward of 100 stays trapped in the pair

The creator pulls proposerReward from the deployer into the new pair. expire() later pulls a second proposerReward from whoever expires the pair and approves only that amount to the optimistic oracle. The proposer can claim the expire-path 100. The prepaid 100 never leaves the pair, and a second cl…

Dec 2021•Other•dos•3 min read

Chain

Other

Category

dos

Date

Dec 2021

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: 10705-c01-trapped-proposer-reward-openzeppelin-uma-audit-l2-bridge. Standalone Foundry PoC and full write-up: 10705-c01-trapped-proposer-reward-openzeppelin-uma-audit-l2-bridge_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/dos/frozen-funds · vuln/logic/missing-check

Key info#

ProtocolUMA Long Short Pair
FindingOpenZeppelin C01, AuditVault 10705
Vulnerable codeLongShortPairCreator.createLongShortPair
Harm100 collateral of proposer reward is unclaimable
Chainlocal synthetic, one chain

TL;DR#

The creator pulls proposerReward from the deployer into the new pair. expire() later pulls a second proposerReward from whoever expires the pair and approves only that amount to the optimistic oracle. The proposer can claim the expire-path 100. The prepaid 100 never leaves the pair, and a second claim pays 0. The control does not take the prepaid transfer. One 100 is paid to the proposer and the pair ends at 0.

The vulnerable code#

SOLIDITY
lsp = new LongShortPair(collateral, oracle, proposerReward);
if (proposerReward > 0) {
    // @> VULN: prepaid proposer reward is pulled into the pair and never forwarded to the oracle
    collateral.safeTransferFrom(msg.sender, address(lsp), proposerReward);
}

expire only approves proposerReward, not the pair's whole balance:

SOLIDITY
collateralToken.safeTransferFrom(msg.sender, address(this), proposerReward);
collateralToken.safeApprove(address(optimisticOracle), proposerReward);

Root cause#

Two independent transfers both intend to fund the same oracle reward. Only the second one is approved to the oracle. The first one has no withdrawal path.

Preconditions#

proposerReward is 100 collateral. The deployer funds creation and a caller later calls expire. Modeled on one chain: the creator, the pair, and the oracle are all in this transaction set. No second chain is required.

Attack walkthrough#

  1. createLongShortPair pulls 100 from the deployer into the pair.
  2. expire pulls another 100 and approves 100 to the oracle.
  3. The oracle takes that 100. The pair balance falls from 200 to 100.
  4. The proposer claims 100. A second claim returns 0.
  5. The prepaid 100 is still in the pair.

Diagrams#

flowchart TD A["Creator pulls 100 into the pair"] --> B["VULN: that 100 is never approved to the oracle"] B --> C["expire pulls a second 100"] C --> D["Oracle takes only the second 100"] D --> E["Proposer claims 100"] E --> F["Prepaid 100 stays in the pair"]

Impact#

100 collateral of proposer reward cannot be claimed. The control skips the creator transfer, expire forwards its 100 to the oracle, and the proposer receives 100 with nothing left in the pair.

Remediation#

Delete the creator's safeTransferFrom into the pair. The reward should be taken once, on expire, and approved to the oracle.

How to reproduce#

BASH
cd 10705-c01-trapped-proposer-reward-openzeppelin-uma-audit-l2-bridge_exp
forge test -vv

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.