Reproduced Exploit
NLX — non-refunded excess fee in `_setPricesFromPriceFeeds`
1. _setPricesFromPriceFeeds reads updateFee = pyth.getUpdateFee(...) and requires msg.value >= updateFee. 2. It calls pyth.updatePriceFeeds{value: updateFee}(...) — only the exact fee is forwarded. 3. Any surplus msg.value - updateFee stays on the oracle module; there is no refund path. 4. HARM in…
Chain
Other
Category
fee
Date
Jul 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: 50881-non-refunded-excess-fee-in-setpricesfrompricefeed-function. Standalone Foundry PoC and full write-up: 50881-non-refunded-excess-fee-in-setpricesfrompricefeed-function_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/fee/missing-refund · vuln/oracle/pyth-update · genome: fee-calculation · direct-drain
Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only
forge-std— no fork, no RPC, noanvil_state. Full trace: output.txt. PoC: test/50881-non-refunded-excess-fee-in-setpricesfrompricefeed-function_exp.sol.
AuditVault taxonomy: lang/solidity · platform/halborn · has/github · has/poc · severity/high · sector/oracle · genome: fee-calculation · direct-drain · pyth-oracle-completeness
Key info#
| Impact | HIGH — any caller that overpays the Pyth update fee permanently loses the excess ETH; it is trapped on the oracle module |
| Protocol | NLX / CoreDAO — price-feed oracle module |
| Vulnerable code | _setPricesFromPriceFeeds — forwards only updateFee to Pyth, never refunds msg.value - updateFee |
| Bug class | Missing refund of excess native fee |
| Finding | Halborn — CoreDAO NLX · #50881 |
| Report | halborn.com/audits/coredao/nlx |
| Source | AuditVault |
| Status | Audit finding — fixed by CoreDAO (refund added). Reproduced here as a standalone local PoC. |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
_setPricesFromPriceFeedsreadsupdateFee = pyth.getUpdateFee(...)and requiresmsg.value >= updateFee.- It calls
pyth.updatePriceFeeds{value: updateFee}(...)— only the exact fee is forwarded. - Any surplus
msg.value - updateFeestays on the oracle module; there is no refund path. - HARM in the PoC: caller sends 0.02 ETH, fee is 0.01 ETH → 0.01 ETH trapped on
OracleModule.
The vulnerable code#
uint updateFee = pyth.getUpdateFee(pythUpdateData);
require(updateFee <= msg.value, "not enough funds to update price feeds");
pyth.updatePriceFeeds{value: updateFee}(pythUpdateData); // @> VULN: excess never refunded
// FIX: refund msg.value - updateFee to msg.sender
Root cause#
Fee collection and fee consumption are asymmetric: the module accepts an arbitrary overpay but only spends updateFee, with no return of the remainder.
Preconditions#
- Caller can invoke the price-feed update path with native value.
- Caller (or UI/integration) sends more than the exact Pyth fee.
Attack walkthrough#
- Attacker (or honest user) calls
setPricesFromPriceFeeds{value: 0.02 ether}. - Module forwards 0.01 ETH to Pyth; 0.01 ETH remains on the module.
- HARM: excess is unreimbursed and stuck.
Diagrams#
Impact#
Users/keepers who overpay the Pyth fee permanently lose the surplus. At scale this is continuous value leakage from every overpaying update.
Sources#
- AuditVault finding #50881
- Halborn report — CoreDAO NLX
- Remediation: NLX-Protocol/nlx-synthetics@c385344
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 50881-non-refunded-excess-fee-in-setpricesfrompricefeed-function_exp (evm-hack-registry mirror).
- AuditVault finding: 50881-non-refunded-excess-fee-in-setpricesfrompricefeed-function.
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.