mirror of
https://github.com/luxfi/graph.git
synced 2026-09-24 00:26:07 +00:00
Red re-verify found the V4 AMM-spoof gate left a parallel bypass.
MEDIUM — V2/V3 AMM handlers were UNGATED: handlePairCreated, handlePoolCreated,
handleSwapV2, handleSwapV3 accepted events from ANY emitter — a parallel path to
seed phantom pairs/pools/swaps and inflate the SAME public Factory/TVL/volume
aggregates the now-gated V4 handlers (0x9999) block. Mirror the V4 gate:
- PairCreated/PoolCreated now require the canonical FactoryV2/FactoryV3 emitter
(the AMM trust root), exactly as V4 requires 0x9999.
- V2/V3 Swaps are emitted by the pair/pool, not the factory, so the gate is
"is the emitter a pool the canonical factory created?" (isKnownPool). The
factory authorizes a pool; only that pool's Swaps are honored. A rogue
factory's PairCreated is rejected, so its pools are never registered, so its
Swaps are rejected — the whole parallel path is closed.
- Pair/pool keys lower-cased at seed so the Swap-side lookup is RPC-casing safe.
- Factory addresses configurable (Config.FactoryV2/V3 + FACTORY_V2/V3 env),
default to canonical Lux mainnet (LuxMainnet.Factory{V2,V3}); main wires env.
INFO — the per-namespace resolver limit lacked the `if limit < 1 { limit = 100 }`
floor that resolvers/dex has, so a negative/zero `first` flowed to ListByType
(`first:-1` = "no limit" in SQLite — an unbounded scan). Brought every pl()/
parseLimit() copy to the dex-canonical body (floor <1->100 + cap 1000); fhe and
platform also gained the previously-missing 1000 cap. All namespaces now identical.
Tests (go test ./...): indexer + resolvers/{dex,exchange,fhe,platform} green.
- TestHandleV2_RejectsNonFactory, TestHandleV3_RejectsNonFactory: rogue
PairCreated/PoolCreated/Swap seeds nothing and does not inflate the factory
aggregate; the canonical-source event IS honored end-to-end.
- TestPL_ClampsFirst / TestParseLimit_ClampsFirst (exchange/fhe/platform):
negative/zero floored to 100, capped at 1000.
163 lines
6.3 KiB
Go
163 lines
6.3 KiB
Go
// Package precompile provides resolvers for Lux EVM precompiled contract calls.
|
|
//
|
|
// Precompiles are native EVM operations at fixed addresses — callable from
|
|
// Solidity, indexed by the graph engine when they emit logs or are called
|
|
// via traces.
|
|
//
|
|
// Address ranges:
|
|
//
|
|
// 0x02xx — Warp (cross-chain messaging)
|
|
// 0x03xx — AI Mining (proof-of-work for AI compute)
|
|
// 0x05xx — Crypto primitives (Poseidon2, Pedersen, Ed25519, ECIES)
|
|
// 0x07xx — FHE (fully homomorphic encryption + ACL + Gateway)
|
|
// 0x09xx — ZK proofs (Groth16, Plonk, Fflonk, Halo2, KZG, IPA, privacy pool)
|
|
// 0x0Axx — Graph (on-chain GraphQL schema registry)
|
|
// 0x92xx — DEX (native orderbook: limit, market, cancel)
|
|
// 0x9003 — Blake3 hash
|
|
// 0xB002 — Ring signatures (Corona)
|
|
//
|
|
// Entities: PrecompileCall, AIWorkProof, FHEOperation, ZKVerification,
|
|
//
|
|
// CryptoOp, WarpCall, DEXOrder, RingSignature, GraphSchemaUpdate
|
|
package precompile
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/luxfi/graph/storage"
|
|
)
|
|
|
|
type ResolverFunc = func(context.Context, *storage.Store, map[string]interface{}) (interface{}, error)
|
|
|
|
func Register(resolvers map[string]ResolverFunc) {
|
|
// Generic precompile call tracking
|
|
resolvers["precompileCall"] = resolvePrecompileCall
|
|
resolvers["precompileCalls"] = resolvePrecompileCalls
|
|
|
|
// AI Mining (0x03xx)
|
|
resolvers["aiWorkProof"] = resolveByType("AIWorkProof")
|
|
resolvers["aiWorkProofs"] = resolveListType("AIWorkProof")
|
|
|
|
// FHE (0x07xx)
|
|
resolvers["fheOperation"] = resolveByType("FHEOperation")
|
|
resolvers["fheOperations"] = resolveListType("FHEOperation")
|
|
resolvers["fheACLGrant"] = resolveByType("FHEACLGrant")
|
|
resolvers["fheACLGrants"] = resolveListType("FHEACLGrant")
|
|
resolvers["fheGatewayRequest"] = resolveByType("FHEGatewayRequest")
|
|
resolvers["fheGatewayRequests"] = resolveListType("FHEGatewayRequest")
|
|
|
|
// Crypto primitives (0x05xx)
|
|
resolvers["cryptoOp"] = resolveByType("CryptoOp")
|
|
resolvers["cryptoOps"] = resolveListType("CryptoOp")
|
|
|
|
// ZK proofs (0x09xx)
|
|
resolvers["zkVerification"] = resolveByType("ZKVerification")
|
|
resolvers["zkVerifications"] = resolveListType("ZKVerification")
|
|
resolvers["privacyPoolOp"] = resolveByType("PrivacyPoolOp")
|
|
resolvers["privacyPoolOps"] = resolveListType("PrivacyPoolOp")
|
|
resolvers["rollupVerification"] = resolveByType("RollupVerification")
|
|
resolvers["rollupVerifications"] = resolveListType("RollupVerification")
|
|
|
|
// Warp (0x02xx)
|
|
resolvers["warpCall"] = resolveByType("WarpCall")
|
|
resolvers["warpCalls"] = resolveListType("WarpCall")
|
|
|
|
// DEX (0x92xx)
|
|
resolvers["dexPrecompileOrder"] = resolveByType("DEXPrecompileOrder")
|
|
resolvers["dexPrecompileOrders"] = resolveListType("DEXPrecompileOrder")
|
|
|
|
// Ring signatures (0xB002)
|
|
resolvers["ringSignature"] = resolveByType("RingSignature")
|
|
resolvers["ringSignatures"] = resolveListType("RingSignature")
|
|
|
|
// Graph schema (0x0Axx)
|
|
resolvers["graphSchemaUpdate"] = resolveByType("GraphSchemaUpdate")
|
|
resolvers["graphSchemaUpdates"] = resolveListType("GraphSchemaUpdate")
|
|
|
|
// PQ crypto (ML-DSA, ML-KEM, SLH-DSA)
|
|
resolvers["pqCryptoOp"] = resolveByType("PQCryptoOp")
|
|
resolvers["pqCryptoOps"] = resolveListType("PQCryptoOp")
|
|
|
|
// Threshold (CGGMP21, FROST)
|
|
resolvers["thresholdOp"] = resolveByType("ThresholdOp")
|
|
resolvers["thresholdOps"] = resolveListType("ThresholdOp")
|
|
|
|
// Stats
|
|
resolvers["precompileStats"] = resolvePrecompileStats
|
|
}
|
|
|
|
// Known precompile address ranges for reference.
|
|
var Addresses = map[string]string{
|
|
"warp": "0x0200000000000000000000000000000000000007",
|
|
"aiMining": "0x0300000000000000000000000000000000000000",
|
|
"poseidon2": "0x0500000000000000000000000000000000000001",
|
|
"pedersen": "0x0500000000000000000000000000000000000003",
|
|
"ed25519": "0x0500000000000000000000000000000000000004",
|
|
"fhe": "0x0700000000000000000000000000000000000000",
|
|
"fheACL": "0x0700000000000000000000000000000000000001",
|
|
"fheGateway": "0x0700000000000000000000000000000000000003",
|
|
"zkVerify": "0x0900000000000000000000000000000000000000",
|
|
"groth16": "0x0900000000000000000000000000000000000001",
|
|
"plonk": "0x0900000000000000000000000000000000000002",
|
|
"fflonk": "0x0900000000000000000000000000000000000003",
|
|
"halo2": "0x0900000000000000000000000000000000000004",
|
|
"kzg": "0x0900000000000000000000000000000000000010",
|
|
"ipa": "0x0900000000000000000000000000000000000012",
|
|
"privacyPool": "0x0900000000000000000000000000000000000020",
|
|
"nullifier": "0x0900000000000000000000000000000000000021",
|
|
"commitment": "0x0900000000000000000000000000000000000022",
|
|
"rangeProof": "0x0900000000000000000000000000000000000023",
|
|
"rollupVerify": "0x0900000000000000000000000000000000000030",
|
|
"stateRoot": "0x0900000000000000000000000000000000000031",
|
|
"batchProof": "0x0900000000000000000000000000000000000032",
|
|
"graph": "0x0A00000000000000000000000000000000000001",
|
|
"blake3": "0x9003",
|
|
"dexLimit": "0x9200",
|
|
"dexMarket": "0x9201",
|
|
"dexCancel": "0x9202",
|
|
"corona": "0xB002",
|
|
}
|
|
|
|
func resolvePrecompileCall(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
|
if id, ok := args["id"]; ok {
|
|
return s.GetByType("PrecompileCall", fmt.Sprint(id))
|
|
}
|
|
return nil, fmt.Errorf("precompileCall requires id")
|
|
}
|
|
|
|
func resolvePrecompileCalls(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
|
return s.ListByType("PrecompileCall", pl(args))
|
|
}
|
|
|
|
func resolvePrecompileStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
|
|
return s.GetByType("PrecompileStats", "1")
|
|
}
|
|
|
|
// Factory helpers for repetitive resolver patterns.
|
|
func resolveByType(entityType string) ResolverFunc {
|
|
return func(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
|
if id, ok := args["id"]; ok {
|
|
return s.GetByType(entityType, fmt.Sprint(id))
|
|
}
|
|
return nil, fmt.Errorf("%s requires id", entityType)
|
|
}
|
|
}
|
|
|
|
func resolveListType(entityType string) ResolverFunc {
|
|
return func(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
|
|
return s.ListByType(entityType, pl(args))
|
|
}
|
|
}
|
|
|
|
func pl(args map[string]interface{}) int {
|
|
limit := 100
|
|
if l, ok := args["first"]; ok {
|
|
fmt.Sscanf(fmt.Sprint(l), "%d", &limit)
|
|
}
|
|
if limit < 1 {
|
|
limit = 100
|
|
}
|
|
return min(limit, 1000)
|
|
}
|