// Copyright (C) 2025, Lux Industries, Inc. All rights reserved. // See the file LICENSE for licensing terms. package graph import ( "fmt" "time" "github.com/luxfi/database" "github.com/luxfi/geth/common" "github.com/luxfi/precompile/contract" gvm "github.com/luxfi/precompile/graph/graphvm" "github.com/luxfi/precompile/modules" "github.com/luxfi/precompile/precompileconfig" ) var _ contract.Configurator = (*configurator)(nil) var _ contract.StatefulPrecompiledContract = (*GraphQLContract)(nil) // ConfigKey is the key used in json config files to specify this precompile config. const ConfigKey = "graphConfig" // Contract addresses for Graph/Query layer (0x0500-0x05FF) // Note: Hashing precompiles (Poseidon2, Pedersen, Blake3) use 0x0500...0001-0004 // Graph precompiles start at 0x0500...0010 to avoid conflicts var ( ContractGraphQLAddress = common.HexToAddress("0x0500000000000000000000000000000000000010") ContractSubscribeAddress = common.HexToAddress("0x0500000000000000000000000000000000000011") ContractCacheAddress = common.HexToAddress("0x0500000000000000000000000000000000000012") ContractIndexAddress = common.HexToAddress("0x0500000000000000000000000000000000000013") ) // GraphQLContract wraps GraphQLPrecompile to implement StatefulPrecompiledContract type GraphQLContract struct { precompile *GraphQLPrecompile } // GraphContractInstance is the singleton var GraphContractInstance = &GraphQLContract{ precompile: NewGraphQLPrecompile(nil), } // SetGraphVMClient configures the Graph precompile to use a real GraphVM client. // This should be called during VM initialization when the database is available. func SetGraphVMClient(db database.Database, config *gvm.GConfig) { client := NewGraphVMClient(db, config) GraphContractInstance.precompile.client = client } // SetGraphVMClientWithChainID configures the client for a specific chain. func SetGraphVMClientWithChainID(db database.Database, config *gvm.GConfig, chainID uint64) { client := NewGraphVMClientWithChainID(db, config, chainID) GraphContractInstance.precompile.client = client } // Module is the precompile module var Module = modules.Module{ ConfigKey: ConfigKey, Address: ContractGraphQLAddress, Contract: GraphContractInstance, Configurator: &configurator{}, } type configurator struct{} func init() { if err := modules.RegisterModule(Module); err != nil { panic(err) } } func (*configurator) MakeConfig() precompileconfig.Config { return new(GraphConfig) } func (*configurator) Configure( chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext, ) error { config, ok := cfg.(*GraphConfig) if !ok { return fmt.Errorf("expected config type %T, got %T: %v", &GraphConfig{}, cfg, cfg) } // Update precompile configuration if config.GChainEndpoint != "" { GraphContractInstance.precompile.config.GChainEndpoint = config.GChainEndpoint } if config.QueryTimeout > 0 { GraphContractInstance.precompile.config.QueryTimeout = time.Duration(config.QueryTimeout) * time.Second } if config.MaxCacheSize > 0 { GraphContractInstance.precompile.config.MaxCacheSize = config.MaxCacheSize } return nil } // GraphConfig implements the precompileconfig.Config interface type GraphConfig struct { Upgrade precompileconfig.Upgrade `json:"upgrade"` GChainEndpoint string `json:"gChainEndpoint,omitempty"` QueryTimeout int `json:"queryTimeout,omitempty"` MaxCacheSize int `json:"maxCacheSize,omitempty"` } func (c *GraphConfig) Key() string { return ConfigKey } func (c *GraphConfig) Timestamp() *uint64 { return c.Upgrade.Timestamp() } func (c *GraphConfig) IsDisabled() bool { return c.Upgrade.Disable } func (c *GraphConfig) Equal(cfg precompileconfig.Config) bool { other, ok := cfg.(*GraphConfig) if !ok { return false } return c.Upgrade.Equal(&other.Upgrade) && c.GChainEndpoint == other.GChainEndpoint && c.QueryTimeout == other.QueryTimeout && c.MaxCacheSize == other.MaxCacheSize } func (c *GraphConfig) Verify(chainConfig precompileconfig.ChainConfig) error { return nil } // Run implements StatefulPrecompiledContract. // // Gas is reserved before any work and refunded down to the actual figure afterwards. The // reservation covers the largest response the precompile may return, so a query never // executes unless its caller has already paid for the worst case — the alternative, // charging for the response after producing it, would have the node perform caller-sized // work before discovering it cannot be paid for. func (c *GraphQLContract) Run( accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool, ) (ret []byte, remainingGas uint64, err error) { reserved := c.precompile.RequiredGas(input) if suppliedGas < reserved { return nil, 0, fmt.Errorf("out of gas: required %d, supplied %d", reserved, suppliedGas) } result, used, runErr := c.precompile.Run(input) if runErr != nil { // A failed run consumes the whole reservation. How much work it managed before // failing is not measurable — an oversized response is rejected only after it has // been produced — so the caller pays the bound it agreed to, which is by // construction at least the work performed. Refunding here would make a malformed // request a cheap way to probe the precompile. return nil, suppliedGas - reserved, runErr } // min keeps the subtraction total. RequiredGas and Run price the same decoded request // with the same functions, and the response term is bounded by the allowance the // reservation already holds, so used <= reserved by construction — // TestGraphQL_UpfrontBoundCoversActual asserts it directly rather than relying on it. return result, suppliedGas - min(used, reserved), nil }