Files
zeekay 705e38c45e fix(indexer): gate V2/V3 AMM handlers to canonical factory; floor parseLimit everywhere
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.
2026-06-21 20:27:48 -07:00

122 lines
4.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package fhe provides resolvers for the T-Chain (ThresholdVM / FHE).
//
// Indexes: DKG ceremonies, threshold decryption requests, encrypted compute
// jobs, FHE ciphertext operations, key shares, FHE policy bindings
// (M-Chain × F-Chain policy gating per LP-114).
//
// Entities: DKGCeremony, DecryptionRequest, ComputeJob, Ciphertext, KeyShare,
//
// FHEPolicy, FHEPolicyBinding.
package fhe
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) {
resolvers["dkgCeremony"] = resolveDKGCeremony
resolvers["dkgCeremonies"] = resolveDKGCeremonies
resolvers["decryptionRequest"] = resolveDecryptionRequest
resolvers["decryptionRequests"] = resolveDecryptionRequests
resolvers["computeJob"] = resolveComputeJob
resolvers["computeJobs"] = resolveComputeJobs
resolvers["ciphertext"] = resolveCiphertext
resolvers["ciphertexts"] = resolveCiphertexts
resolvers["keyShare"] = resolveKeyShare
resolvers["keyShares"] = resolveKeyShares
resolvers["fheStats"] = resolveFHEStats
resolvers["fhePolicy"] = resolveFHEPolicy
resolvers["fhePolicies"] = resolveFHEPolicies
resolvers["fhePolicyBinding"] = resolveFHEPolicyBinding
resolvers["fhePolicyBindings"] = resolveFHEPolicyBindings
}
func resolveDKGCeremony(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
if id, ok := args["id"]; ok {
return s.GetByType("DKGCeremony", fmt.Sprint(id))
}
return nil, fmt.Errorf("dkgCeremony requires id")
}
func resolveDKGCeremonies(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
return s.ListByType("DKGCeremony", parseLimit(args))
}
func resolveDecryptionRequest(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
if id, ok := args["id"]; ok {
return s.GetByType("DecryptionRequest", fmt.Sprint(id))
}
return nil, fmt.Errorf("decryptionRequest requires id")
}
func resolveDecryptionRequests(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
return s.ListByType("DecryptionRequest", parseLimit(args))
}
func resolveComputeJob(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
if id, ok := args["id"]; ok {
return s.GetByType("ComputeJob", fmt.Sprint(id))
}
return nil, fmt.Errorf("computeJob requires id")
}
func resolveComputeJobs(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
return s.ListByType("ComputeJob", parseLimit(args))
}
func resolveCiphertext(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
if id, ok := args["id"]; ok {
return s.GetByType("Ciphertext", fmt.Sprint(id))
}
return nil, fmt.Errorf("ciphertext requires id")
}
func resolveCiphertexts(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
return s.ListByType("Ciphertext", parseLimit(args))
}
func resolveKeyShare(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
if id, ok := args["id"]; ok {
return s.GetByType("KeyShare", fmt.Sprint(id))
}
return nil, fmt.Errorf("keyShare requires id")
}
func resolveKeyShares(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
return s.ListByType("KeyShare", parseLimit(args))
}
func resolveFHEStats(_ context.Context, s *storage.Store, _ map[string]interface{}) (interface{}, error) {
return s.GetByType("FHEStats", "1")
}
// FHEPolicy: an encrypted-policy descriptor anchored on M-Chain
// (subjectId, policyHash, threshold, expiresAt).
func resolveFHEPolicy(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
if id, ok := args["id"]; ok {
return s.GetByType("FHEPolicy", fmt.Sprint(id))
}
return nil, fmt.Errorf("fhePolicy requires id")
}
func resolveFHEPolicies(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
return s.ListByType("FHEPolicy", parseLimit(args))
}
// FHEPolicyBinding: binding of an FHE policy to an on-chain resource
// (token, vault, identity) — per LP-114 M-Chain × F-Chain integration.
func resolveFHEPolicyBinding(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
if id, ok := args["id"]; ok {
return s.GetByType("FHEPolicyBinding", fmt.Sprint(id))
}
return nil, fmt.Errorf("fhePolicyBinding requires id")
}
func resolveFHEPolicyBindings(_ context.Context, s *storage.Store, args map[string]interface{}) (interface{}, error) {
return s.ListByType("FHEPolicyBinding", parseLimit(args))
}
func parseLimit(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)
}