Refs #46
PR: feat/21-mempool-monitor
File: crates/charon-scanner/src/mempool.rs
Types: OracleUpdate, PreSignedLiquidation, PendingCache::insert()
Problem
OracleUpdate.new_price is Option: None for updatePrice and updateAssetPrice (single-arg refresh calls that do not carry the new price in calldata), Some(price) for setDirectPrice and setUnderlyingPrice.
The doc comment on new_price states the caller "must re-read the oracle after the tx confirms" for the None case. However, PendingCache::insert() accepts any PreSignedLiquidation unconditionally. Nothing prevents a caller from inserting a pre-signed liquidation triggered by an OracleUpdate with new_price = None.
updatePrice and updateAssetPrice are the calls made by the ResilientOracle's push-based keepers — they are the MOST COMMON oracle update type. Pre-signing a liquidation against a None price update means the pre-sign was built against the current (pre-update) price, which is the wrong state. If the update is a price decrease, the pre-sign may liquidate a currently-healthy borrower.
Required fix
Option A: Make OracleUpdate an enum to structurally distinguish the two cases:
pub enum OracleUpdate {
Refresh { tx_hash: B256, selector: FixedBytes<4>, asset: Address },
DirectUpdate { tx_hash: B256, selector: FixedBytes<4>, asset: Address, price: U256 },
}
This forces downstream code to explicitly handle the Refresh variant and prevents accidental pre-signing.
Option B: At minimum, add a runtime guard in the downstream handler that skips PendingCache::insert() when new_price is None, with a prominent doc comment on insert() stating this contract.
Refs #46
PR: feat/21-mempool-monitor
File: crates/charon-scanner/src/mempool.rs
Types: OracleUpdate, PreSignedLiquidation, PendingCache::insert()
Problem
OracleUpdate.new_price is Option: None for updatePrice and updateAssetPrice (single-arg refresh calls that do not carry the new price in calldata), Some(price) for setDirectPrice and setUnderlyingPrice.
The doc comment on new_price states the caller "must re-read the oracle after the tx confirms" for the None case. However, PendingCache::insert() accepts any PreSignedLiquidation unconditionally. Nothing prevents a caller from inserting a pre-signed liquidation triggered by an OracleUpdate with new_price = None.
updatePrice and updateAssetPrice are the calls made by the ResilientOracle's push-based keepers — they are the MOST COMMON oracle update type. Pre-signing a liquidation against a None price update means the pre-sign was built against the current (pre-update) price, which is the wrong state. If the update is a price decrease, the pre-sign may liquidate a currently-healthy borrower.
Required fix
Option A: Make OracleUpdate an enum to structurally distinguish the two cases:
This forces downstream code to explicitly handle the Refresh variant and prevents accidental pre-signing.
Option B: At minimum, add a runtime guard in the downstream handler that skips PendingCache::insert() when new_price is None, with a prominent doc comment on insert() stating this contract.