mirror of
https://github.com/labrinyang/lp-terminal.git
synced 2026-08-15 21:58:07 +00:00
fix(indexer): stop a manipulated pool from minting prices
A price used to propagate from any pool with enough depth on the priced side, which let a thin pool with one inflated side mint a price for its other token and carry it across the graph. Depth is now measured on the side that is already credibly priced, and a pool-priced side may claim at most 100x that depth. Guard rails around it, all in one place: prices travel at most 3 hops from a GT/anchor seed, |tick| beyond 700k is a broken init rather than a market, stored prices must land in a plausibility band, and a TVL above $1B is treated as corrupt instead of as a whale. Adds a log tail so pool stats follow chain events instead of polling, a demand-gated frontpage, and a last-good fallback so a failed refresh serves the previous stat rather than a hole. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3a392cb141
commit
d73eaf873a
@@ -0,0 +1,49 @@
|
||||
// Event-driven state refresh. A pool's reserves change only when it emits an
|
||||
// event, so we don't reread on a timer — we ask the chain which pools changed.
|
||||
// One eth_getLogs (v2 Sync, v3 Swap/Mint/Burn, any address) since the last block
|
||||
// we processed yields exactly the pools that traded; we reread only those. Cost
|
||||
// tracks trading activity, not catalog size — this is what lets the 6-hourly
|
||||
// census over 100k+ dust pools, and the timed hot/active sweeps, go away.
|
||||
//
|
||||
// Two deliberate choices:
|
||||
// - reread state, don't apply the logs' own deltas: whatever the chain returns
|
||||
// now is the truth, so a reorg needs no unwinding and a missed log self-heals
|
||||
// on the pool's next event (the daily census is the longstop).
|
||||
// - gated on the same POOLS-tab demand signal as the frontpage sweep — the only
|
||||
// consumer of fresh state is a watching user, and a getLogs every 12s costs
|
||||
// 75 CU. Idle → no polling. On resume we jump the cursor to head instead of
|
||||
// replaying the idle backlog: freshness wants the current state, not history.
|
||||
import { numberToHex, toEventSelector } from 'viem'
|
||||
import { TUNE, log } from './config'
|
||||
import { pc } from './rpc'
|
||||
import { computeTvlFor, sweepState } from './state'
|
||||
import { kvGet, kvSet, poolsHitAgeMs } from './store'
|
||||
|
||||
export const TOPICS = [
|
||||
toEventSelector('Sync(uint112,uint112)'), // univ2 reserves (fires on every mint/burn/swap)
|
||||
toEventSelector('Swap(address,address,int256,int256,uint160,uint128,int24)'), // univ3 price + liquidity
|
||||
toEventSelector('Mint(address,address,int24,int24,uint128,uint256,uint256)'), // univ3 liquidity add
|
||||
toEventSelector('Burn(address,int24,int24,uint128,uint256,uint256)'), // univ3 liquidity remove
|
||||
]
|
||||
|
||||
/** reread every catalog pool that emitted a tracked event since the last run */
|
||||
export async function logtail(): Promise<void> {
|
||||
if (poolsHitAgeMs() > TUNE.frontpageGateMs) return // nobody watching — don't spend getLogs
|
||||
const head = Number(await pc.getBlockNumber())
|
||||
const cursor = Number(kvGet('logtail_block') ?? 0)
|
||||
const from = cursor && head - cursor <= TUNE.logtailMaxBlocks ? cursor + 1 : head // skip idle backlog
|
||||
if (from > head) return
|
||||
|
||||
const logs = (await pc.request({
|
||||
method: 'eth_getLogs',
|
||||
params: [{ fromBlock: numberToHex(from), toBlock: numberToHex(head), topics: [TOPICS] }],
|
||||
})) as { address: string }[]
|
||||
|
||||
const dirty = [...new Set(logs.map((l) => l.address.toLowerCase()))]
|
||||
if (dirty.length) {
|
||||
const swept = await sweepState(dirty) // sweepState ignores addresses not in the catalog
|
||||
computeTvlFor(dirty)
|
||||
log(`[logtail] blocks ${from}-${head}: ${dirty.length} pools emitted, ${swept} in catalog`)
|
||||
}
|
||||
kvSet('logtail_block', String(head))
|
||||
}
|
||||
Reference in New Issue
Block a user