Reproduced Exploit
Royco RecipeOrderbook — a null Weiroll market still pulls incentives
fillLPOrder only reverts when fillAmount is strictly greater than the remaining quantity. A fill of 0 against an order that was never created therefore passes. A market id that was never created reads as the zero struct, and RewardStyle.Upfront is 0, so the function still transferFroms each tokenAm…
Chain
Other
Category
logic
Date
Aug 2024
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: 46675-unintended-behavior-in-recipe-orderbook-for-null-weiroll-mar. Standalone Foundry PoC and full write-up: 46675-unintended-behavior-in-recipe-orderbook-for-null-weiroll-mar_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/wrong-condition · vuln/logic/missing-check
Key info#
| Protocol | Royco RecipeOrderbook |
| Finding | Cantina, Kurt Barry, AuditVault 46675 |
| Vulnerable code | RecipeOrderbook.fillLPOrder |
| Harm | 1000 INC moved from the filler to the LP of an order whose market was never created |
| Chain | local synthetic |
TL;DR#
fillLPOrder only reverts when fillAmount is strictly greater than the remaining quantity. A fill of 0 against an order that was never created therefore passes. A market id that was never created reads as the zero struct, and RewardStyle.Upfront is 0, so the function still transferFroms each tokenAmountsRequested from the filler to order.lp. In this run that moves 1000 INC. The fixed book requires the remaining quantity to be non-zero first, and the same call reverts with the filler's second 1000 left in place.
The vulnerable code#
if (fillAmount > orderHashToRemainingQuantity[orderHash]) revert NotEnoughRemainingQuantity();
orderHashToRemainingQuantity[orderHash] -= fillAmount;
WeirollMarket memory market = marketIDToWeirollMarket[order.targetMarketID];
if (market.rewardStyle == RewardStyle.Upfront) {
// @> VULN: a never-created market is rewardStyle 0 (Upfront), so a zero fill still pulls tokenAmountsRequested
MockERC20(order.tokensRequested[i]).transferFrom(msg.sender, order.lp, order.tokenAmountsRequested[i]);
}
The recommended check is require(orderHashToRemainingQuantity[orderHash] > 0) before the transfer.
Root cause#
Two defaults line up. A missing order has remaining quantity 0, and 0 > 0 is false, so a zero fill is not rejected. A missing market has rewardStyle == 0, which is Upfront, so the incentive pull runs even though no market and no order exist.
Preconditions#
The filler has approved the orderbook for the requested token. No market has to be created and the order does not have to be signed or stored. The caller is the one who pays.
Attack walkthrough#
- The filler holds 2000 INC. Order hash 999 has remaining quantity 0.
fillLPOrderis called withfillAmount0,targetMarketID999, andtokenAmountsRequestedof 1000 INC.- The remaining-quantity comparison does not revert. The null market is treated as Upfront.
- 1000 INC moves from the filler to the order LP.
- The checked book reverts on the same order. The filler still holds the other 1000.
Diagrams#
Impact#
A filler who submits a zero-amount fill against a market that was never created pays the full requested incentive. Here that is 1000 INC sent to an LP who never posted an order.
Remediation#
Require orderHashToRemainingQuantity[orderHash] > 0 before adjusting quantity or transferring incentives, and reject a market id that was never created.
How to reproduce#
cd 46675-unintended-behavior-in-recipe-orderbook-for-null-weiroll-mar_exp
forge test -vv
Sources#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/46675-unintended-behavior-in-recipe-orderbook-for-null-weiroll-mar.md
- Cantina report: https://cdn.cantina.xyz/reports/cantina_royco_august2024.pdf
- Royco fixed the zero-quantity fill in commit
8b0d931d(noted in the finding).
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 46675-unintended-behavior-in-recipe-orderbook-for-null-weiroll-mar_exp (evm-hack-registry mirror).
- AuditVault finding: 46675-unintended-behavior-in-recipe-orderbook-for-null-weiroll-mar.
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.