Files

854 lines
33 KiB
Go

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package modelregistry
import (
"encoding/binary"
"reflect"
"testing"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompile/contract"
"github.com/luxfi/precompile/modules"
"github.com/luxfi/precompile/precompileconfig"
"github.com/stretchr/testify/require"
)
// ---------------------------------------------------------------------------
// fixtures
// ---------------------------------------------------------------------------
var (
govA = common.HexToAddress("0x00000000000000000000000000000000000000A1")
govB = common.HexToAddress("0x00000000000000000000000000000000000000A2")
govC = common.HexToAddress("0x00000000000000000000000000000000000000A3")
outsider = common.HexToAddress("0x00000000000000000000000000000000000000BB")
beluga = common.HexToHash("0x0000000000000000000000000000000000000000000000000042424265677561")
weightV1 = common.HexToHash("0xABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890")
weightV2 = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
)
const selectorUnknown uint32 = 0xDEADBEEF
func ptr64(v uint64) *uint64 { return &v }
// countingState is the mock StateDB plus a write counter. Reading state back is
// not enough to prove a refused call wrote nothing — a write of the identical
// value is invisible to a read. The counter proves SetState was never reached.
type countingState struct {
*mockState
writes int
}
var _ contract.StateDB = (*countingState)(nil)
func (c *countingState) SetState(addr common.Address, k, v common.Hash) common.Hash {
c.writes++
return c.mockState.SetState(addr, k, v)
}
func (c *countingState) snap() map[common.Hash]common.Hash {
out := make(map[common.Hash]common.Hash, len(c.m))
for k, v := range c.m {
out[k] = v
}
return out
}
// seeded builds a registry whose genesis governance set is admins.
func seeded(t *testing.T, admins ...common.Address) (*countingState, mockAccessible) {
t.Helper()
st := &countingState{mockState: newMockState()}
require.NoError(t, (&configurator{}).Configure(nil, &Config{Admins: admins}, st, nil))
st.writes = 0 // genesis seeding is not what the caller under test is doing
return st, mockAccessible{s: st}
}
// run executes input, paying exactly what RequiredGas asks.
func run(acc mockAccessible, caller common.Address, in []byte, readOnly bool) ([]byte, error) {
ret, _, err := ModelRegistryPrecompile.Run(
acc, caller, ContractAddress, in, ModelRegistryPrecompile.RequiredGas(in), readOnly)
return ret, err
}
// refuses asserts input is rejected AND that the rejection was inert: no write
// attempted, and the whole state map byte-identical afterwards.
func refuses(t *testing.T, st *countingState, acc mockAccessible, caller common.Address, in []byte, readOnly bool) error {
t.Helper()
before := st.snap()
st.writes = 0
_, err := run(acc, caller, in, readOnly)
require.Error(t, err)
require.Zero(t, st.writes, "refused call reached SetState")
require.Equal(t, before, st.snap(), "refused call mutated state")
return err
}
// adopted reads the registry through the precompile ABI, which is what a
// contract on-chain sees.
func adopted(t *testing.T, acc mockAccessible, name common.Hash) (common.Hash, common.Hash) {
t.Helper()
ret, err := run(acc, outsider, getApprovedInput(name), true)
require.NoError(t, err)
require.Len(t, ret, 64)
return common.BytesToHash(ret[0:32]), common.BytesToHash(ret[32:64])
}
func isAdminABI(t *testing.T, acc mockAccessible, a common.Address) bool {
t.Helper()
ret, err := run(acc, outsider, isAdminInput(a), true)
require.NoError(t, err)
require.Len(t, ret, 32)
return ret[31] == 1
}
// truncate returns a call whose selector is intact but whose body is one byte
// short of the minimum the handler needs.
func truncate(selector uint32, bodyLen int) []byte {
return append(sel(selector), make([]byte, bodyLen)...)
}
// ---------------------------------------------------------------------------
// module wiring
// ---------------------------------------------------------------------------
func TestModuleRegistration(t *testing.T) {
byKey, ok := modules.GetPrecompileModule(ConfigKey)
require.True(t, ok, "package init did not register the module under its config key")
require.Equal(t, ContractAddress, byKey.Address)
byAddr, ok := modules.GetPrecompileModuleByAddress(ContractAddress)
require.True(t, ok, "module is not reachable by address")
require.Equal(t, ConfigKey, byAddr.ConfigKey)
// Genesis JSON routes by config key; the address routes EVM calls. Both must
// land on this package's contract or the precompile is unreachable.
require.Same(t, ModelRegistryPrecompile, byAddr.Contract)
require.Equal(t, ConfigKey, Module.Configurator.MakeConfig().Key())
// The address is consensus: every chain config, Solidity binding and
// off-chain reader already names it. Slot 0x02 of the AI reserved range
// 0x0300…0000..00FF is MODEL_REGISTRY; moving it would leave callers
// talking to an empty account rather than to a precompile.
require.Equal(t,
common.HexToAddress("0x0300000000000000000000000000000000000002"),
ContractAddress)
// The config key is the genesis/upgrade JSON key. Renaming it does not
// fail — the host simply never finds a config for this module, so the
// precompile silently never activates.
require.Equal(t, "modelRegistryConfig", ConfigKey)
// So are the selectors. Distinctness matters as much as the values: two
// selectors sharing a number would make the second method unreachable,
// because Run dispatches on the first matching arm.
selectors := map[string]uint32{
"adopt": SelectorAdopt,
"getApproved": SelectorGetApproved,
"isAdmin": SelectorIsAdmin,
"setAdmin": SelectorSetAdmin,
}
require.Equal(t, map[string]uint32{
"adopt": 0x01000000,
"getApproved": 0x02000000,
"isAdmin": 0x03000000,
"setAdmin": 0x04000000,
}, selectors)
seen := map[uint32]string{}
for name, s := range selectors {
prev, dup := seen[s]
require.False(t, dup, "%s and %s share selector %#x", prev, name, s)
seen[s] = name
}
require.NotContains(t, seen, selectorUnknown, "the test's unknown selector is a real method")
}
func TestMakeConfigIsFreshAndEmpty(t *testing.T) {
c := (&configurator{}).MakeConfig()
require.NotNil(t, c)
cfg, ok := c.(*Config)
require.True(t, ok, "MakeConfig must produce the type Configure asserts on")
require.Empty(t, cfg.Admins, "a fresh config must seed no governance")
require.Nil(t, cfg.Timestamp())
require.False(t, cfg.IsDisabled())
require.NoError(t, cfg.Verify(nil))
require.Equal(t, ConfigKey, cfg.Key())
// The host unmarshals genesis JSON into whatever MakeConfig hands back. If
// it returned a shared value, one chain's admin set would leak into another.
other := (&configurator{}).MakeConfig()
require.NotSame(t, cfg, other)
cfg.Admins = append(cfg.Admins, govA)
require.Empty(t, other.(*Config).Admins)
}
func TestConfigMirrorsUpgrade(t *testing.T) {
for _, tc := range []struct {
name string
up precompileconfig.Upgrade
}{
{"unscheduled", precompileconfig.Upgrade{}},
{"scheduled", precompileconfig.Upgrade{BlockTimestamp: ptr64(1_700_000_000)}},
{"disabled", precompileconfig.Upgrade{BlockTimestamp: ptr64(9), Disable: true}},
} {
t.Run(tc.name, func(t *testing.T) {
c := &Config{Upgrade: tc.up, Admins: []common.Address{govA}}
require.Equal(t, ConfigKey, c.Key())
require.Equal(t, tc.up.Timestamp(), c.Timestamp())
require.Equal(t, tc.up.Disable, c.IsDisabled())
// Verify gates genesis JSON; this config carries nothing to reject.
require.NoError(t, c.Verify(nil))
})
}
// Timestamp aliases the upgrade's pointer rather than copying a snapshot.
c := &Config{Upgrade: precompileconfig.Upgrade{BlockTimestamp: ptr64(5)}}
require.Equal(t, uint64(5), *c.Timestamp())
}
// ---------------------------------------------------------------------------
// Config.Equal
// ---------------------------------------------------------------------------
// foreignConfig is an unrelated Config implementation. Equal must reject it on
// type, never by reading fields it does not have.
type foreignConfig struct{}
func (foreignConfig) Key() string { return "foreign" }
func (foreignConfig) Timestamp() *uint64 { return nil }
func (foreignConfig) IsDisabled() bool { return false }
func (foreignConfig) Verify(precompileconfig.ChainConfig) error { return nil }
func (foreignConfig) Equal(precompileconfig.Config) bool { return false }
var _ precompileconfig.Config = foreignConfig{}
func TestConfigEqual(t *testing.T) {
up := func(ts *uint64, disable bool) precompileconfig.Upgrade {
return precompileconfig.Upgrade{BlockTimestamp: ts, Disable: disable}
}
for _, tc := range []struct {
name string
a, b *Config
want bool
}{
{"zero values", &Config{}, &Config{}, true},
{"nil vs empty admin slice", &Config{}, &Config{Admins: []common.Address{}}, true},
{"same everything",
&Config{Upgrade: up(ptr64(7), true), Admins: []common.Address{govA, govB}},
&Config{Upgrade: up(ptr64(7), true), Admins: []common.Address{govA, govB}}, true},
// Upgrade half.
{"timestamp differs",
&Config{Upgrade: up(ptr64(7), false)}, &Config{Upgrade: up(ptr64(8), false)}, false},
{"timestamp set vs unscheduled",
&Config{Upgrade: up(ptr64(7), false)}, &Config{Upgrade: up(nil, false)}, false},
{"disable differs",
&Config{Upgrade: up(ptr64(7), true)}, &Config{Upgrade: up(ptr64(7), false)}, false},
// Admins half.
{"admin count differs (none vs one)",
&Config{}, &Config{Admins: []common.Address{govA}}, false},
{"admin count differs (one vs two)",
&Config{Admins: []common.Address{govA}},
&Config{Admins: []common.Address{govA, govB}}, false},
{"same length, different member",
&Config{Admins: []common.Address{govA}},
&Config{Admins: []common.Address{govB}}, false},
{"same length, one member swapped",
&Config{Admins: []common.Address{govA, govB}},
&Config{Admins: []common.Address{govA, govC}}, false},
// Order is part of the config document: two genesis files listing the
// same admins in different orders are different files, and Equal is how
// the host detects a config it has not already applied.
{"same members, different order",
&Config{Admins: []common.Address{govA, govB}},
&Config{Admins: []common.Address{govB, govA}}, false},
// Both halves differ at once.
{"upgrade and admins both differ",
&Config{Upgrade: up(ptr64(1), false), Admins: []common.Address{govA}},
&Config{Upgrade: up(ptr64(2), true), Admins: []common.Address{govB}}, false},
} {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.want, tc.a.Equal(tc.b))
require.Equal(t, tc.want, tc.b.Equal(tc.a), "Equal must be symmetric")
})
}
full := &Config{Upgrade: up(ptr64(7), true), Admins: []common.Address{govA}}
require.True(t, full.Equal(full), "Equal must be reflexive")
require.False(t, full.Equal(nil), "a nil config is not equal to a real one")
require.False(t, full.Equal(foreignConfig{}), "a foreign Config type is never equal")
}
// TestConfigEqualComparesEveryField fails the day a field is added to Config
// without teaching Equal about it — the classic way an upgrade check goes blind.
func TestConfigEqualComparesEveryField(t *testing.T) {
base := Config{
Upgrade: precompileconfig.Upgrade{BlockTimestamp: ptr64(7)},
Admins: []common.Address{govA},
}
mutate := map[string]func(*Config){
"Upgrade": func(c *Config) { c.Upgrade.BlockTimestamp = ptr64(8) },
"Admins": func(c *Config) { c.Admins = []common.Address{govB} },
}
rt := reflect.TypeOf(base)
require.Equal(t, len(mutate), rt.NumField(),
"Config's field set changed; extend Equal and this table")
for i := range rt.NumField() {
name := rt.Field(i).Name
change, known := mutate[name]
require.True(t, known, "no mutation covers field %s", name)
t.Run(name, func(t *testing.T) {
other := base
change(&other)
require.False(t, base.Equal(&other), "Equal ignores %s", name)
require.False(t, other.Equal(&base), "Equal ignores %s", name)
})
}
}
// ---------------------------------------------------------------------------
// Configure
// ---------------------------------------------------------------------------
func TestConfigureSeedsGenesisAdmins(t *testing.T) {
for _, tc := range []struct {
name string
admins []common.Address
}{
{"no admins", nil},
{"empty slice", []common.Address{}},
{"one admin", []common.Address{govA}},
{"several admins", []common.Address{govA, govB, govC}},
{"duplicate entries", []common.Address{govA, govA}},
} {
t.Run(tc.name, func(t *testing.T) {
st := &countingState{mockState: newMockState()}
require.NoError(t, (&configurator{}).Configure(nil, &Config{Admins: tc.admins}, st, nil))
acc := mockAccessible{s: st}
for _, a := range tc.admins {
require.True(t, IsAdmin(st, a), "%s was listed at genesis but is not admin", a)
require.True(t, isAdminABI(t, acc, a), "ABI disagrees with the Go helper for %s", a)
}
// Seeding is not "everyone is admin".
require.False(t, IsAdmin(st, outsider))
require.False(t, isAdminABI(t, acc, outsider))
if len(tc.admins) == 0 {
require.Zero(t, st.writes, "an empty governance set must touch no state")
} else {
require.NotZero(t, st.writes)
// The seeding is real governance, not just a flag: a seeded
// admin can actually adopt.
_, err := run(acc, tc.admins[0], adoptInput(beluga, weightV1, 1), false)
require.NoError(t, err)
}
})
}
}
func TestConfigureRejectsForeignConfig(t *testing.T) {
for _, tc := range []struct {
name string
cfg precompileconfig.Config
}{
{"nil config", nil},
{"foreign type", foreignConfig{}},
} {
t.Run(tc.name, func(t *testing.T) {
st := &countingState{mockState: newMockState()}
err := (&configurator{}).Configure(nil, tc.cfg, st, nil)
require.ErrorContains(t, err, "unexpected config type")
require.Zero(t, st.writes, "a rejected config must not seed anything")
require.Empty(t, st.m)
})
}
}
// ---------------------------------------------------------------------------
// access control
// ---------------------------------------------------------------------------
func TestNonAdminCannotAdopt(t *testing.T) {
st, acc := seeded(t, govA)
// Nothing adopted yet.
ver, weight := adopted(t, acc, beluga)
require.Equal(t, common.Hash{}, ver)
require.Equal(t, common.Hash{}, weight)
err := refuses(t, st, acc, outsider, adoptInput(beluga, weightV1, 1), false)
require.ErrorContains(t, err, "not an admin")
// State is still empty when read back through the ABI.
ver, weight = adopted(t, acc, beluga)
require.Equal(t, common.Hash{}, ver, "a refused adopt published a version")
require.Equal(t, common.Hash{}, weight, "a refused adopt published weights")
// And a refused caller cannot overwrite an existing adoption either.
_, err = run(acc, govA, adoptInput(beluga, weightV1, 1), false)
require.NoError(t, err)
err = refuses(t, st, acc, outsider, adoptInput(beluga, weightV2, 99), false)
require.ErrorContains(t, err, "not an admin")
ver, weight = adopted(t, acc, beluga)
require.Equal(t, uint64(1), binary.BigEndian.Uint64(ver[24:32]))
require.Equal(t, weightV1, weight, "a refused adopt replaced the live weights")
}
func TestNonAdminCannotSetAdmin(t *testing.T) {
st, acc := seeded(t, govA)
err := refuses(t, st, acc, outsider, setAdminInput(outsider, true), false)
require.ErrorContains(t, err, "not an admin")
require.False(t, IsAdmin(st, outsider), "an outsider promoted itself")
// Nor can an outsider strip a real admin.
err = refuses(t, st, acc, outsider, setAdminInput(govA, false), false)
require.ErrorContains(t, err, "not an admin")
require.True(t, IsAdmin(st, govA), "an outsider demoted a genesis admin")
}
// TestAuthorizationPrecedesParsing pins the order of the checks in Run:
// read-only, then admin, then body length. A caller who is not allowed to write
// must be turned away before the body is even looked at, so no malformed-input
// path can ever run ahead of the authorization check.
func TestAuthorizationPrecedesParsing(t *testing.T) {
st, acc := seeded(t, govA)
for _, tc := range []struct {
name string
caller common.Address
in []byte
readOnly bool
want string
}{
{"adopt, outsider, short body", outsider, truncate(SelectorAdopt, 95), false, "not an admin"},
{"setAdmin, outsider, short body", outsider, truncate(SelectorSetAdmin, 63), false, "not an admin"},
{"adopt, admin, read-only, short body", govA, truncate(SelectorAdopt, 95), true, "read-only"},
{"setAdmin, admin, read-only, short body", govA, truncate(SelectorSetAdmin, 63), true, "read-only"},
{"adopt, outsider, read-only", outsider, adoptInput(beluga, weightV1, 1), true, "read-only"},
{"setAdmin, outsider, read-only", outsider, setAdminInput(outsider, true), true, "read-only"},
} {
t.Run(tc.name, func(t *testing.T) {
err := refuses(t, st, acc, tc.caller, tc.in, tc.readOnly)
require.ErrorContains(t, err, tc.want)
})
}
}
func TestAdminAdoptsAndPromotes(t *testing.T) {
st, acc := seeded(t, govA)
// govB is not admin yet, so it cannot adopt.
require.False(t, isAdminABI(t, acc, govB))
err := refuses(t, st, acc, govB, adoptInput(beluga, weightV1, 1), false)
require.ErrorContains(t, err, "not an admin")
// govA promotes govB.
ret, err := run(acc, govA, setAdminInput(govB, true), false)
require.NoError(t, err)
require.Equal(t, boolWord(true), ret)
require.True(t, IsAdmin(st, govB))
require.True(t, isAdminABI(t, acc, govB))
// The promotion is real: govB can now adopt, and the adoption is visible.
ret, err = run(acc, govB, adoptInput(beluga, weightV2, 42), false)
require.NoError(t, err)
require.Equal(t, boolWord(true), ret)
ver, weight := adopted(t, acc, beluga)
require.Equal(t, uint64(42), binary.BigEndian.Uint64(ver[24:32]))
require.Equal(t, weightV2, weight)
// Promotion does not spill onto anyone else.
require.False(t, IsAdmin(st, govC))
require.False(t, IsAdmin(st, outsider))
// A newly promoted admin can promote in turn: the privilege is not
// special-cased to the genesis set.
_, err = run(acc, govB, setAdminInput(govC, true), false)
require.NoError(t, err)
require.True(t, IsAdmin(st, govC))
}
func TestSetAdminDemotes(t *testing.T) {
st, acc := seeded(t, govA, govB)
require.True(t, IsAdmin(st, govB))
ret, err := run(acc, govA, setAdminInput(govB, false), false)
require.NoError(t, err)
require.Equal(t, boolWord(true), ret)
// Demotion clears the slot rather than leaving a stale non-zero value.
require.Equal(t, common.Hash{}, st.GetState(ContractAddress, adminKey(govB)))
require.False(t, IsAdmin(st, govB))
require.False(t, isAdminABI(t, acc, govB))
// A demoted admin is refused on both privileged entry points.
err = refuses(t, st, acc, govB, adoptInput(beluga, weightV1, 1), false)
require.ErrorContains(t, err, "not an admin")
err = refuses(t, st, acc, govB, setAdminInput(govB, true), false)
require.ErrorContains(t, err, "not an admin")
// Demotion is not contagious.
require.True(t, IsAdmin(st, govA))
// A re-promotion restores the privilege, so demotion is a toggle and not a
// one-way burn of the address.
_, err = run(acc, govA, setAdminInput(govB, true), false)
require.NoError(t, err)
require.True(t, IsAdmin(st, govB))
}
// TestSoleAdminCanDemoteItself records that governance has no floor: the last
// admin may remove itself, after which nothing can ever adopt or re-admit an
// admin again, because both paths require an existing admin. Configure only
// runs at activation, so there is no way back.
func TestSoleAdminCanDemoteItself(t *testing.T) {
st, acc := seeded(t, govA)
_, err := run(acc, govA, setAdminInput(govA, false), false)
require.NoError(t, err)
require.False(t, IsAdmin(st, govA))
err = refuses(t, st, acc, govA, setAdminInput(govA, true), false)
require.ErrorContains(t, err, "not an admin")
err = refuses(t, st, acc, govA, adoptInput(beluga, weightV1, 1), false)
require.ErrorContains(t, err, "not an admin")
}
// ---------------------------------------------------------------------------
// read-only
// ---------------------------------------------------------------------------
func TestReadOnlyRefusesWritesAndAllowsReads(t *testing.T) {
st, acc := seeded(t, govA)
_, err := run(acc, govA, adoptInput(beluga, weightV1, 3), false)
require.NoError(t, err)
// Writes: refused even for a genesis admin, and inert.
err = refuses(t, st, acc, govA, adoptInput(beluga, weightV2, 9), true)
require.ErrorContains(t, err, "read-only")
err = refuses(t, st, acc, govA, setAdminInput(govB, true), true)
require.ErrorContains(t, err, "read-only")
err = refuses(t, st, acc, govA, setAdminInput(govA, false), true)
require.ErrorContains(t, err, "read-only")
// Nothing moved.
require.True(t, IsAdmin(st, govA))
require.False(t, IsAdmin(st, govB))
ver, weight := adopted(t, acc, beluga)
require.Equal(t, uint64(3), binary.BigEndian.Uint64(ver[24:32]))
require.Equal(t, weightV1, weight)
// Reads still answer under read-only — that is the whole point of eth_call.
require.True(t, isAdminABI(t, acc, govA))
require.False(t, isAdminABI(t, acc, govB))
}
// ---------------------------------------------------------------------------
// Run: refusals
// ---------------------------------------------------------------------------
func TestRunRefusalsAreInert(t *testing.T) {
st, acc := seeded(t, govA)
// Give the state something to lose.
_, err := run(acc, govA, adoptInput(beluga, weightV1, 5), false)
require.NoError(t, err)
for _, tc := range []struct {
name string
in []byte
want string
}{
{"nil input", nil, "input too short"},
{"one byte", []byte{0x01}, "input too short"},
{"two bytes", []byte{0x01, 0x00}, "input too short"},
{"three bytes", []byte{0x01, 0x00, 0x00}, "input too short"},
{"unknown selector", sel(selectorUnknown), "unknown selector"},
{"unknown selector with body", append(sel(selectorUnknown), make([]byte, 96)...), "unknown selector"},
{"adopt body one byte short", truncate(SelectorAdopt, 95), "adopt: input too short"},
{"getApproved body one byte short", truncate(SelectorGetApproved, 31), "getApproved: input too short"},
{"isAdmin body one byte short", truncate(SelectorIsAdmin, 31), "isAdmin: input too short"},
{"setAdmin body one byte short", truncate(SelectorSetAdmin, 63), "setAdmin: input too short"},
{"adopt with no body", sel(SelectorAdopt), "adopt: input too short"},
{"getApproved with no body", sel(SelectorGetApproved), "getApproved: input too short"},
{"isAdmin with no body", sel(SelectorIsAdmin), "isAdmin: input too short"},
{"setAdmin with no body", sel(SelectorSetAdmin), "setAdmin: input too short"},
} {
t.Run(tc.name, func(t *testing.T) {
// Caller is an admin under a writable call, so the only thing that
// can reject these is the decoding itself.
err := refuses(t, st, acc, govA, tc.in, false)
require.ErrorContains(t, err, tc.want)
})
}
// The live adoption survived every one of them.
ver, weight := adopted(t, acc, beluga)
require.Equal(t, uint64(5), binary.BigEndian.Uint64(ver[24:32]))
require.Equal(t, weightV1, weight)
require.True(t, IsAdmin(st, govA))
}
// TestRunAcceptsMinimumBodies is the other half of the length checks: one byte
// less is refused, exactly enough is accepted. Without this, a handler that
// demanded one byte too many would still pass the refusal table.
func TestRunAcceptsMinimumBodies(t *testing.T) {
_, acc := seeded(t, govA)
for _, tc := range []struct {
name string
in []byte
}{
{"adopt", truncate(SelectorAdopt, 96)},
{"getApproved", truncate(SelectorGetApproved, 32)},
{"isAdmin", truncate(SelectorIsAdmin, 32)},
{"setAdmin", truncate(SelectorSetAdmin, 64)},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := run(acc, govA, tc.in, false)
require.NoError(t, err)
})
}
// Trailing bytes past the minimum are ignored, not rejected.
_, err := run(acc, govA, append(adoptInput(beluga, weightV1, 1), make([]byte, 33)...), false)
require.NoError(t, err)
ver, weight := adopted(t, acc, beluga)
require.Equal(t, uint64(1), binary.BigEndian.Uint64(ver[24:32]))
require.Equal(t, weightV1, weight)
}
// ---------------------------------------------------------------------------
// gas
// ---------------------------------------------------------------------------
func TestRequiredGasPricesBySelector(t *testing.T) {
gasOf := ModelRegistryPrecompile.RequiredGas
adopt := gasOf(adoptInput(beluga, weightV1, 1))
setAdmin := gasOf(setAdminInput(govB, true))
getApproved := gasOf(getApprovedInput(beluga))
isAdmin := gasOf(isAdminInput(govA))
// Relative shape, not literals: nothing is free; a read of two slots costs
// more than a read of one; a write costs more than a read; a two-slot write
// costs more than a one-slot write.
require.Positive(t, isAdmin)
require.Greater(t, getApproved, isAdmin)
require.Greater(t, setAdmin, getApproved)
require.Greater(t, adopt, setAdmin)
// Price follows the selector alone — same selector, different body, same
// price — so a caller cannot buy a discount by varying arguments.
require.Equal(t, adopt, gasOf(adoptInput(weightV2, beluga, 1<<40)))
require.Equal(t, setAdmin, gasOf(setAdminInput(outsider, false)))
require.Equal(t, isAdmin, gasOf(isAdminInput(outsider)))
require.Equal(t, getApproved, gasOf(getApprovedInput(weightV1)))
// Nor by truncating the body: the price is fixed before the body is read.
require.Equal(t, adopt, gasOf(truncate(SelectorAdopt, 0)))
require.Equal(t, setAdmin, gasOf(truncate(SelectorSetAdmin, 1)))
// Undecodable input falls to the module's default price. It is never free —
// probing the precompile costs gas — and never priced as a write.
for _, in := range [][]byte{nil, {}, {0x01}, {0x01, 0x02, 0x03}, sel(selectorUnknown)} {
def := gasOf(in)
require.Equal(t, getApproved, def, "default price drifted from the read price")
require.Positive(t, def)
require.Less(t, def, setAdmin)
}
// RequiredGas is pure: it may not consult state, so repeating a call after
// the registry has changed must return the same number.
st, acc := seeded(t, govA)
in := adoptInput(beluga, weightV1, 1)
_, err := run(acc, govA, in, false)
require.NoError(t, err)
require.NotEmpty(t, st.m)
require.Equal(t, adopt, gasOf(in))
}
// TestRunDebitsExactlyRequiredGas pins the accounting: one gas short refuses and
// consumes everything; anything at or above the price leaves the surplus.
func TestRunDebitsExactlyRequiredGas(t *testing.T) {
for _, tc := range []struct {
name string
in []byte
}{
{"adopt", adoptInput(beluga, weightV1, 1)},
{"getApproved", getApprovedInput(beluga)},
{"isAdmin", isAdminInput(govA)},
{"setAdmin", setAdminInput(govB, true)},
{"unknown selector", sel(selectorUnknown)},
{"short input", []byte{0x01}},
{"truncated adopt body", truncate(SelectorAdopt, 95)},
} {
t.Run(tc.name, func(t *testing.T) {
need := ModelRegistryPrecompile.RequiredGas(tc.in)
require.Positive(t, need)
// One gas short: refused, no gas returned, nothing written.
st, acc := seeded(t, govA)
before := st.snap()
ret, remaining, err := ModelRegistryPrecompile.Run(
acc, govA, ContractAddress, tc.in, need-1, false)
require.ErrorContains(t, err, "out of gas")
require.Nil(t, ret)
require.Zero(t, remaining)
require.Zero(t, st.writes, "an out-of-gas call still wrote state")
require.Equal(t, before, st.snap())
// Exactly the price: the call is decided on its merits and the whole
// supply is consumed.
_, acc = seeded(t, govA)
_, remaining, _ = ModelRegistryPrecompile.Run(
acc, govA, ContractAddress, tc.in, need, false)
require.Zero(t, remaining)
// Surplus is returned untouched, whether the call succeeds or is
// refused after the gas check.
_, acc = seeded(t, govA)
_, remaining, _ = ModelRegistryPrecompile.Run(
acc, govA, ContractAddress, tc.in, need+1234, false)
require.Equal(t, uint64(1234), remaining)
})
}
}
// ---------------------------------------------------------------------------
// storage layout
// ---------------------------------------------------------------------------
// TestStateKeysAreStable pins the storage layout. These slots are consensus:
// a live chain's governance set and adopted models sit at exactly these
// addresses, so changing the hash, a domain prefix, or the argument order
// relocates every existing entry to a slot nothing reads. That failure is
// silent — the registry would come back from the upgrade with no admins and no
// adopted model, and no error anywhere. Change these values only as a
// deliberate, migrated hard fork.
func TestStateKeysAreStable(t *testing.T) {
require.Equal(t,
common.HexToHash("0x1b008afa4aa1ed15a87c1c0a6811b61d26d57c311d618395d3892b1ee4ebc119"),
adminKey(govA))
require.Equal(t,
common.HexToHash("0x4d8cb13131d6a12dff452503f667e4dd4d65433ae656738a58f24cba0579729d"),
verKey(beluga))
require.Equal(t,
common.HexToHash("0xb0c71026c4b8bc2ef8f1ab3b0beda93d82b007d628336ceb8463aceaf62603fc"),
weightKey(beluga))
// The three domains are separated: the same bytes read as an address and as
// a model name never land in the same slot, so adopting a model can never
// mint an admin and promoting an admin can never publish weights.
asName := common.BytesToHash(govA.Bytes())
slots := []common.Hash{adminKey(govA), verKey(asName), weightKey(asName)}
for i := range slots {
for j := i + 1; j < len(slots); j++ {
require.NotEqual(t, slots[i], slots[j], "storage domains %d and %d collide", i, j)
}
}
// Distinct subjects get distinct slots.
require.NotEqual(t, adminKey(govA), adminKey(govB))
require.NotEqual(t, verKey(beluga), verKey(weightV1))
require.NotEqual(t, weightKey(beluga), weightKey(weightV1))
// enabledHash is a non-zero word, which is the whole test IsAdmin applies.
require.NotEqual(t, common.Hash{}, enabledHash)
}
func TestAdoptIsPerModelName(t *testing.T) {
_, acc := seeded(t, govA)
other := common.HexToHash("0x02")
_, err := run(acc, govA, adoptInput(beluga, weightV1, 1), false)
require.NoError(t, err)
_, err = run(acc, govA, adoptInput(other, weightV2, 2), false)
require.NoError(t, err)
ver, weight := adopted(t, acc, beluga)
require.Equal(t, uint64(1), binary.BigEndian.Uint64(ver[24:32]))
require.Equal(t, weightV1, weight)
ver, weight = adopted(t, acc, other)
require.Equal(t, uint64(2), binary.BigEndian.Uint64(ver[24:32]))
require.Equal(t, weightV2, weight)
// An unadopted name reads as empty, which is how a caller tells "no brain
// chosen yet" from "this one".
ver, weight = adopted(t, acc, common.HexToHash("0x03"))
require.Equal(t, common.Hash{}, ver)
require.Equal(t, common.Hash{}, weight)
// The admin flag and the model slots cannot collide: adopting a model whose
// name is an admin address must not confer or revoke adminship.
nameOfAdmin := common.BytesToHash(govA.Bytes())
_, err = run(acc, govA, adoptInput(nameOfAdmin, weightV2, 7), false)
require.NoError(t, err)
require.True(t, IsAdmin(acc.s, govA))
require.False(t, IsAdmin(acc.s, govB))
}
// TestGetApprovedNarrowsTheVersionWord records that the two read paths disagree
// above 2^64: the ABI returns the full 32-byte word that was adopted, while the
// Go helper reports only its low 64 bits.
func TestGetApprovedNarrowsTheVersionWord(t *testing.T) {
st, acc := seeded(t, govA)
// A version word whose value does not fit in 64 bits.
hiVersion := common.HexToHash("0x0000000000000000000000000000000000000001000000000000000000000000")
in := append(append(sel(SelectorAdopt), beluga[:]...), append(hiVersion[:], weightV1[:]...)...)
_, err := run(acc, govA, in, false)
require.NoError(t, err)
ver, weight := adopted(t, acc, beluga)
require.Equal(t, hiVersion, ver, "the ABI must return the exact word that was adopted")
require.Equal(t, weightV1, weight)
v, w := GetApproved(st, beluga)
require.Equal(t, weightV1, w)
require.Zero(t, v, "GetApproved reports the low 64 bits only; callers must not "+
"treat it as the full adopted version")
// Inside 64 bits the two paths agree, which is the supported range.
_, err = run(acc, govA, adoptInput(beluga, weightV2, 1<<40), false)
require.NoError(t, err)
v, w = GetApproved(st, beluga)
require.Equal(t, uint64(1)<<40, v)
require.Equal(t, weightV2, w)
}
// TestSetAdminTreatsAnyNonZeroWordAsTrue pins the bool decoding: Solidity sends
// 0 or 1, but the handler accepts any non-zero word as "enable" rather than
// rejecting a non-canonical encoding.
func TestSetAdminTreatsAnyNonZeroWordAsTrue(t *testing.T) {
st, acc := seeded(t, govA)
enable := make([]byte, 32)
enable[0] = 0x02 // non-canonical true, high byte set
in := append(append(sel(SelectorSetAdmin), word(govB.Bytes())...), enable...)
_, err := run(acc, govA, in, false)
require.NoError(t, err)
require.True(t, IsAdmin(st, govB))
// The address is decoded from the low 20 bytes; the 12 padding bytes are
// ignored rather than rejected.
padded := append(append(sel(SelectorIsAdmin), make([]byte, 12)...), govB.Bytes()...)
padded[4] = 0xFF
ret, err := run(acc, outsider, padded, true)
require.NoError(t, err)
require.Equal(t, byte(1), ret[31])
}