Files
evm/params/extras/determinism_test.go
zeekayandHanzo Dev d260656efc evm: gate DEX 0x9999 genesis injection on protocol ActivationTime (preserve pre-2025 genesis)
The AlwaysOn DEX settlement precompile (0x9999) had its EXTCODESIZE marker
baked into EVERY chain's genesis state unconditionally (parent==nil in
ApplyPrecompileActivations), mutating the genesis state root. This changed the
genesis hash of every chain genesised before the DEX activation, breaking RLP
re-import of the real archived Lux mainnet (0x3f4fa2a0), Lux testnet
(0x1c5fe377) and Zoo mainnet (0x7c548af4) — all re-genesised Nov 2024, before
the Dec-2025 activation.

Fix: an AlwaysOn system precompile carries a protocol ActivationTime (Dec 2025
for 0x9999, set by the registry bridge — a fork-like constant, not per-net
config). Its genesis marker is installed ONLY IF ActivationTime <= the genesis
timestamp; otherwise it activates later, on the block transition that crosses
ActivationTime during history replay. Dispatch (GetExtrasRules) is gated on the
same timestamp so a codeless address never dispatches. A chain born before
activation reproduces its ORIGINAL genesis byte-for-byte; a fresh chain born
at/after activation still gets 0x9999 at genesis.

Verified against the real RLP block-0 of all three chains (byte-identical
reproduction) plus a fresh post-activation chain. Corrects two pre-existing
tests that had hardcoded the buggy with-0x9999 hashes as "canonical".

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 16:50:11 -07:00

166 lines
6.3 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package extras
import (
"encoding/json"
"testing"
"github.com/luxfi/evm/precompile/modules"
"github.com/luxfi/geth/common"
"github.com/stretchr/testify/require"
)
// TestAllGenesisPrecompilesDeterminism verifies that AllGenesisPrecompiles
// returns consistent results across multiple calls.
func TestAllGenesisPrecompilesDeterminism(t *testing.T) {
// Call multiple times and verify results are identical
result1 := AllGenesisPrecompiles()
result2 := AllGenesisPrecompiles()
require.Equal(t, len(result1), len(result2), "AllGenesisPrecompiles should return same number of precompiles")
for key, config1 := range result1 {
config2, ok := result2[key]
require.True(t, ok, "key %s should exist in both results", key)
require.Equal(t, config1.Key(), config2.Key(), "config keys should match")
// All genesis configs should have timestamp = 0
require.NotNil(t, config1.Timestamp(), "genesis config timestamp should not be nil")
require.Equal(t, uint64(0), *config1.Timestamp(), "genesis config timestamp should be 0")
}
// Verify all registered NON-AlwaysOn modules are present, and that NO AlwaysOn
// module is present. AlwaysOn precompiles (e.g. the 0x9999 DEX settlement money
// path) are activated by the AlwaysOn mechanism (protocol-timestamp-gated dispatch +
// activation-crossing marker), NOT by a timestamp-0 genesis config; emitting one would create a
// second, conflicting activation path.
for _, module := range modules.RegisteredModules() {
_, ok := result1[module.ConfigKey]
if module.AlwaysOn {
require.Falsef(t, ok,
"AlwaysOn module %s must NOT be in AllGenesisPrecompiles — its activation is the AlwaysOn mechanism, not a genesis config",
module.ConfigKey)
continue
}
require.True(t, ok, "module %s should be in AllGenesisPrecompiles", module.ConfigKey)
}
}
// TestGenesisPrecompileBuilders_SkipAlwaysOn pins the INFO1 guard: the genesis-config
// builders (AllGenesisPrecompiles and ChainConfig.SetAllGenesisPrecompiles) must never
// emit a timestamp-0 genesis config for an AlwaysOn module. Writing one would create a
// SECOND, conflicting activation path for an AlwaysOn system precompile (0x9999) — its
// Configurator would run alongside the AlwaysOn mechanism (protocol-timestamp-gated
// dispatch + activation-crossing marker). The invariant is enforced positively (skip any module with AlwaysOn)
// so it holds even if an AlwaysOn module is later registered into this package's view.
func TestGenesisPrecompileBuilders_SkipAlwaysOn(t *testing.T) {
fromFunc := AllGenesisPrecompiles()
var cfg ChainConfig
cfg.SetAllGenesisPrecompiles()
for _, module := range modules.RegisteredModules() {
if !module.AlwaysOn {
continue
}
_, inFunc := fromFunc[module.ConfigKey]
require.Falsef(t, inFunc,
"AllGenesisPrecompiles must skip AlwaysOn module %s — dated-fork activation, not genesis", module.ConfigKey)
_, inMethod := cfg.GenesisPrecompiles[module.ConfigKey]
require.Falsef(t, inMethod,
"SetAllGenesisPrecompiles must skip AlwaysOn module %s — dated-fork activation, not genesis", module.ConfigKey)
}
}
// TestPrecompilesMarshalDeterministic verifies that MarshalJSONDeterministic
// produces consistent output regardless of map iteration order.
func TestPrecompilesMarshalDeterministic(t *testing.T) {
precompiles := AllGenesisPrecompiles()
// Marshal multiple times and verify output is identical
json1, err := precompiles.MarshalJSONDeterministic()
require.NoError(t, err)
json2, err := precompiles.MarshalJSONDeterministic()
require.NoError(t, err)
require.Equal(t, string(json1), string(json2), "deterministic marshal should produce identical output")
// Verify it's valid JSON
var parsed map[string]json.RawMessage
err = json.Unmarshal(json1, &parsed)
require.NoError(t, err)
require.Equal(t, len(precompiles), len(parsed), "parsed JSON should have same number of keys")
}
// TestAddressBookResolution verifies that address resolution works correctly
// with addressBook overrides.
func TestAddressBookResolution(t *testing.T) {
// Skip if no modules are registered (shouldn't happen in normal build)
registeredMods := modules.RegisteredModules()
if len(registeredMods) == 0 {
t.Skip("no precompile modules registered")
}
// Use the first registered module for testing
testModule := registeredMods[0]
testKey := testModule.ConfigKey
defaultAddr := testModule.Address
// Create a different address for override
overrideAddr := common.HexToAddress("0x1234567890123456789012345678901234567890")
config := &ChainConfig{
AddressBook: map[string]common.Address{
testKey: overrideAddr,
},
}
// With addressBook override, should return override address
addr := config.GetPrecompileAddress(testKey)
require.Equal(t, overrideAddr, addr, "addressBook should override module address")
// Without addressBook override, should return module default
configNoOverride := &ChainConfig{}
addrDefault := configNoOverride.GetPrecompileAddress(testKey)
require.Equal(t, defaultAddr, addrDefault, "without addressBook, should use module address")
// Test LegacyWarpAddress constant is valid
require.NotEqual(t, common.Address{}, LegacyWarpAddress, "LegacyWarpAddress should be set")
}
// TestEmptyPrecompilesMarshal verifies that empty Precompiles marshal correctly.
func TestEmptyPrecompilesMarshal(t *testing.T) {
empty := Precompiles{}
json, err := empty.MarshalJSONDeterministic()
require.NoError(t, err)
require.Equal(t, "{}", string(json), "empty Precompiles should marshal to {}")
}
// TestChainConfigGenesisPrecompilesRoundTrip tests that GenesisPrecompiles
// survive JSON round-trip through custom marshal/unmarshal.
func TestChainConfigGenesisPrecompilesRoundTrip(t *testing.T) {
original := &ChainConfig{
GenesisPrecompiles: AllGenesisPrecompiles(),
}
// Marshal
jsonBytes, err := json.Marshal(original)
require.NoError(t, err)
// Unmarshal
var restored ChainConfig
err = json.Unmarshal(jsonBytes, &restored)
require.NoError(t, err)
// Verify GenesisPrecompiles are restored
require.Equal(t, len(original.GenesisPrecompiles), len(restored.GenesisPrecompiles),
"GenesisPrecompiles should have same length after round-trip")
for key := range original.GenesisPrecompiles {
_, ok := restored.GenesisPrecompiles[key]
require.True(t, ok, "key %s should exist after round-trip", key)
}
}