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…
Chain
Other
Category
dos
Date
Dec 2021
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: 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-registrymirror.
Vulnerability classes: vuln/dos/frozen-funds · vuln/logic/missing-check
Key info#
| Protocol | UMA Long Short Pair |
| Finding | OpenZeppelin C01, AuditVault 10705 |
| Vulnerable code | LongShortPairCreator.createLongShortPair |
| Harm | 100 collateral of proposer reward is unclaimable |
| Chain | local 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#
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:
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#
createLongShortPairpulls 100 from the deployer into the pair.expirepulls another 100 and approves 100 to the oracle.- The oracle takes that 100. The pair balance falls from 200 to 100.
- The proposer claims 100. A second claim returns 0.
- The prepaid 100 is still in the pair.
Diagrams#
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#
cd 10705-c01-trapped-proposer-reward-openzeppelin-uma-audit-l2-bridge_exp
forge test -vv
Sources#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/10705-c01-trapped-proposer-reward-openzeppelin-uma-audit-l2-bridge.md
- OpenZeppelin: https://blog.openzeppelin.com/uma-audit-l2-bridges/
- Creator: https://github.com/UMAprotocol/protocol/blob/d4e7ea22159b2eed8e39d5b86ce0026ea3b8b995/packages/core/contracts/financial-templates/long-short-pair/LongShortPairCreator.sol#L112-L113
- Pair: https://github.com/UMAprotocol/protocol/blob/d4e7ea22159b2eed8e39d5b86ce0026ea3b8b995/packages/core/contracts/financial-templates/long-short-pair/LongShortPair.sol#L369-L372
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 10705-c01-trapped-proposer-reward-openzeppelin-uma-audit-l2-bridge_exp (evm-hack-registry mirror).
- AuditVault finding: 10705-c01-trapped-proposer-reward-openzeppelin-uma-audit-l2-bridge.
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.