Files
evm/core/precompile_env_call_test.go
zeekay 1e41171291 precompile env: wire 0x9999 ERC-20 Call surface to the DEX precompile
ROOT CAUSE: the external DEX 0x9999 settlement precompile's ERC-20 leg (transferFrom/
transfer/balanceOf on the token contract) needs the EVM Call surface, but the adapter
chain that delivers its AccessibleState narrowed Call away:

  geth evmPrecompileEnv{Call}
   -> evm/core accessibleStateAdapter{env}        (env captured, not exposed)
   -> evm/registry accessibleStateBridge          (GetPrecompileEnv() => nil)
   -> luxfi/precompile contract.PrecompileEnvironment (no Call)

so poolStateAdapter.callable() got nil and every ERC-20 deposit/withdraw refused with
ErrERC20VaultUnavailable (fail-secure — never minted). Native LUX settlement was
unaffected because it uses GetBalance/SubBalance, already plumbed through the bridges.

FIX (minimal, off the shared many-implementer interfaces):
- evm/core accessibleStateAdapter.GetPrecompileEnv() exposes the geth env it already
  holds (concrete accessor, NOT a widening of the internal contract.AccessibleState).
- evm/registry accessibleStateBridge.GetPrecompileEnv() type-asserts that accessor and
  returns precompileEnvBridge, which adds the callableEnv-shaped Call the precompile
  type-asserts; nil (fail-secure) when no geth env is present.
- Call forwards with NO caller proxying: the token's msg.sender is the precompile self
  (0x9999), matching the depositor's approve(0x9999) allowance. Proxying would check
  the wrong allowance and break the pull.

geth is UNTOUCHED: vm.PrecompileEnvironment already declares Call; the DEX never runs
through geth's own accessibleStateAdapter (that path serves only the PQ precompiles).

Regression guards: core pins *accessibleStateAdapter satisfies the envProvider accessor
and returns its env; registry pins the bridge returns a non-nil callable env that
forwards Call (value passed, ZERO options) when wired, and untyped-nil otherwise
(no-env + typed-nil traps).
2026-06-22 14:37:01 -07:00

60 lines
2.7 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package core
import (
"testing"
"github.com/luxfi/geth/core/vm"
)
// precompile_env_call_test.go pins the PRODUCER side of the ERC-20 settlement env
// wiring: the EVM hands a precompile its AccessibleState as *accessibleStateAdapter,
// which captures the geth vm.PrecompileEnvironment (the only object with a Call
// surface). The registry bridge (precompile/registry/bridge.go) type-asserts the
// concrete envProvider accessor below to forward that Call to the external DEX 0x9999
// precompile so its ERC-20 leg (transferFrom / transfer / balanceOf on the token
// contract) executes on-chain. If *accessibleStateAdapter ever stops exposing the env,
// GetPrecompileEnv() reverts to nil and ERC-20 settlement silently refuses with
// ErrERC20VaultUnavailable — so this guard fails loudly on that drift.
// envProvider is the EXACT accessor precompile/registry/bridge.go type-asserts. Keep
// it byte-identical so this test fails if either side's signature drifts.
type envProvider interface {
GetPrecompileEnv() vm.PrecompileEnvironment
}
// TestAccessibleStateAdapter_ExposesPrecompileEnv is the regression guard: the
// concrete *accessibleStateAdapter the EVM gives precompiles MUST satisfy envProvider
// and MUST return the same non-nil geth env it was constructed with. If it returns nil
// or fails the assertion, the registry bridge cannot forward Call and ERC-20
// settlement is dead on-chain.
func TestAccessibleStateAdapter_ExposesPrecompileEnv(t *testing.T) {
// Compile-time assertion (primary guard against signature drift).
var _ envProvider = (*accessibleStateAdapter)(nil)
// Runtime: construct the adapter exactly as RunStateful does and confirm it hands
// back the SAME env. A nil sentinel env is sufficient — the load-bearing fact is
// the accessor returns what it captured, not nil.
env := &sentinelPrecompileEnv{}
a := &accessibleStateAdapter{env: env}
var asProvider envProvider = a
got := asProvider.GetPrecompileEnv()
if got == nil {
t.Fatal("GetPrecompileEnv() returned nil — registry bridge cannot forward Call; ERC-20 settlement would refuse")
}
if got != vm.PrecompileEnvironment(env) {
t.Fatal("GetPrecompileEnv() did not return the captured env")
}
}
// sentinelPrecompileEnv is a do-nothing vm.PrecompileEnvironment used only to prove
// the adapter returns the env it was given. Its methods are never invoked by this
// test (identity check only), so they panic if ever called — keeping the sentinel
// honest about being a marker, not a behavioural mock.
type sentinelPrecompileEnv struct {
vm.PrecompileEnvironment // embed the interface; all methods panic via the nil embedding if called
}