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.
122 lines
4.8 KiB
Go
122 lines
4.8 KiB
Go
// 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)
|
||
}
|