Reproduced Exploit
Vertex PerpEngine — settlement pays the whole AMM instead of the LP share
getSettlementState adds the AMM's entire base and quote into the trader's PnL. Only amountLp / supply of that inventory belongs to the trader. With price 1, 100 base, 100 quote, and a 10 of 100 LP position, settlement pays 200 quote. The fair figure is 20. A second engine that scales by the LP shar…
Chain
Other
Category
logic
Date
Jan 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: 48378-improper-settlement-calculation-ottersec-none-vertex-protoco. Standalone Foundry PoC and full write-up: 48378-improper-settlement-calculation-ottersec-none-vertex-protoco_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/price-calculation · vuln/logic/wrong-condition
Key info#
| Protocol | Vertex PerpEngine |
| Finding | OtterSec, Shiva Shankar, AuditVault 48378 |
| Vulnerable code | PerpEngine.settle position PnL |
| Harm | Paid 200 quote. The trader's 10 percent LP share is fairly 20. Extra 180. |
| Chain | local synthetic |
TL;DR#
getSettlementState adds the AMM's entire base and quote into the trader's PnL. Only amountLp / supply of that inventory belongs to the trader. With price 1, 100 base, 100 quote, and a 10 of 100 LP position, settlement pays 200 quote. The fair figure is 20. A second engine that scales by the LP share pays 20.
The vulnerable code#
// @> VULN: adds the entire AMM base and quote, not amountLp/supply of the pool
positionPnlX18 = priceX18.mul(balance.amountX18 + ammBaseX18) + balance.vQuoteBalanceX18 + ammQuoteX18;
The fix multiplies ammBaseX18 and ammQuoteX18 by amountLp / supply before adding them.
Root cause#
The settlement formula treats pool inventory as if the settler owned all of it. Every LP who settles is credited with the same full pool value, so the quote vault can be drained down to the last settler's full-pool claim.
Preconditions#
The trader has a non-zero LP balance that is less than the total supply, and the AMM holds base and quote. No oracle update is required. Price is 1.0 in this run so the quote units are the raw inventory.
Attack walkthrough#
- The trader deposits 10 LP against a supply of 100. Position base and vQuote are 0.
- The pool holds 100 base and 100 quote.
settleat price 1 computes1 * (0 + 100) + 0 + 100 = 200and transfers 200 quote.- The fair PnL is
1 * (0 + 10) + 10 = 20. - The control engine pays 20.
Diagrams#
Impact#
The settler receives 200 quote instead of 20. The extra 180 is other LPs' inventory. Repeating the settle for every LP would credit the full pool once per LP.
Remediation#
Scale ammBase and ammQuote by amountLp / supply before adding them to the position PnL.
How to reproduce#
cd 48378-improper-settlement-calculation-ottersec-none-vertex-protoco_exp
forge test -vv
Sources#
- AuditVault finding: https://github.com/Auditware/AuditVault/blob/main/findings/48378-improper-settlement-calculation-ottersec-none-vertex-protoco.md
- Vertex: https://vertexprotocol.io/
- The finding says the PnL was changed to the
amountLp/supplyratio in patch #130.
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 48378-improper-settlement-calculation-ottersec-none-vertex-protoco_exp (evm-hack-registry mirror).
- AuditVault finding: 48378-improper-settlement-calculation-ottersec-none-vertex-protoco.
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.