From e5d7c2f19be9ee334633b94a16da733f3b3474c5 Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Wed, 5 Aug 2026 12:34:03 +0800 Subject: [PATCH 1/2] add offline EVM load scenarios --- generator/offline/erc20_transfer.go | 120 ++++++++++++++++++++++++++ generator/offline/scenario.go | 86 +++++++++++++++++++ generator/offline/scenario_test.go | 126 ++++++++++++++++++++++++++++ generator/offline/transfer.go | 44 ++++++++++ go.mod | 13 ++- go.sum | 17 ++++ 6 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 generator/offline/erc20_transfer.go create mode 100644 generator/offline/scenario.go create mode 100644 generator/offline/scenario_test.go create mode 100644 generator/offline/transfer.go diff --git a/generator/offline/erc20_transfer.go b/generator/offline/erc20_transfer.go new file mode 100644 index 0000000..b21b23e --- /dev/null +++ b/generator/offline/erc20_transfer.go @@ -0,0 +1,120 @@ +package offline + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm/runtime" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + + "github.com/sei-protocol/sei-load/generator/bindings" +) + +const erc20BalancesSlot = uint64(4) + +var loadERC20Runtime = sync.OnceValues(buildERC20Runtime) + +type erc20TransferScenario struct { + cfg Config + signer ethtypes.Signer + contract *abi.ABI +} + +func newERC20TransferScenario(cfg Config) (*erc20TransferScenario, error) { + contract, err := bindings.ERC20MetaData.GetAbi() + if err != nil { + return nil, fmt.Errorf("parse ERC20 ABI: %w", err) + } + return &erc20TransferScenario{ + cfg: cfg, + signer: ethtypes.LatestSignerForChainID(cfg.ChainID), + contract: contract, + }, nil +} + +func (s *erc20TransferScenario) SetupGenesis(state GenesisWriter) error { + code, err := ERC20RuntimeCode() + if err != nil { + return err + } + state.SetCode(s.cfg.ERC20Contract, code) + return nil +} + +func (s *erc20TransferScenario) SeedSender(state GenesisWriter, sender common.Address) { + state.SetBalance(sender, s.cfg.SenderBalance) + state.SetState(s.cfg.ERC20Contract, ERC20BalanceSlot(sender), common.BigToHash(s.cfg.TransferValue)) +} + +func (s *erc20TransferScenario) BuildTransaction(key *ecdsa.PrivateKey, nonce uint64, recipient common.Address) (*ethtypes.Transaction, error) { + if key == nil { + return nil, fmt.Errorf("sender private key is required") + } + data, err := s.contract.Pack("transfer", recipient, s.cfg.TransferValue) + if err != nil { + return nil, fmt.Errorf("pack ERC20 transfer: %w", err) + } + tx := ethtypes.NewTx(ðtypes.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(s.cfg.GasPrice), + Gas: s.cfg.GasLimit, + To: &s.cfg.ERC20Contract, + Value: new(big.Int), + Data: data, + }) + return ethtypes.SignTx(tx, s.signer, key) +} + +// ERC20RuntimeCode returns a copy of the runtime produced by the committed +// sei-load ERC20 creation bytecode. +func ERC20RuntimeCode() ([]byte, error) { + code, err := loadERC20Runtime() + if err != nil { + return nil, err + } + return append([]byte(nil), code...), nil +} + +// ERC20BalanceSlot returns the storage key for _balances[owner] in the +// committed sei-load ERC20 contract. +func ERC20BalanceSlot(owner common.Address) common.Hash { + var encoded [64]byte + copy(encoded[12:32], owner.Bytes()) + new(big.Int).SetUint64(erc20BalancesSlot).FillBytes(encoded[32:]) + return crypto.Keccak256Hash(encoded[:]) +} + +func buildERC20Runtime() ([]byte, error) { + contract, err := bindings.ERC20MetaData.GetAbi() + if err != nil { + return nil, fmt.Errorf("parse ERC20 ABI: %w", err) + } + constructor, err := contract.Constructor.Inputs.Pack("LoadToken", "LT") + if err != nil { + return nil, fmt.Errorf("pack ERC20 constructor: %w", err) + } + initCode := append(common.FromHex(bindings.ERC20Bin), constructor...) + code, _, _, err := runtime.Create(initCode, &runtime.Config{ + ChainConfig: params.AllEthashProtocolChanges, + Origin: common.HexToAddress("0x1"), + BlockNumber: big.NewInt(1), + Time: 1_700_000_000, + GasLimit: 10_000_000, + GasPrice: new(big.Int), + Value: new(big.Int), + BaseFee: new(big.Int), + }) + if err != nil { + return nil, fmt.Errorf("execute ERC20 constructor: %w", err) + } + if len(code) == 0 { + return nil, fmt.Errorf("ERC20 constructor returned empty runtime") + } + return code, nil +} diff --git a/generator/offline/scenario.go b/generator/offline/scenario.go new file mode 100644 index 0000000..19f3f53 --- /dev/null +++ b/generator/offline/scenario.go @@ -0,0 +1,86 @@ +// Package offline provides backend-neutral load scenarios for executors that +// consume raw Ethereum transactions and an explicitly seeded genesis state. +package offline + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +const ( + Transfer = "transfer" + ERC20Transfer = "erc20-transfer" +) + +// GenesisWriter is the state surface needed to prepare an offline scenario. +// Implementations may ignore writes that are irrelevant to their backend. +type GenesisWriter interface { + SetBalance(common.Address, *big.Int) + SetCode(common.Address, []byte) + SetState(common.Address, common.Hash, common.Hash) +} + +// Config contains transaction and genesis settings shared by offline scenarios. +type Config struct { + ChainID *big.Int + GasPrice *big.Int + SenderBalance *big.Int + TransferValue *big.Int + GasLimit uint64 + ERC20Contract common.Address +} + +// Scenario builds signed transactions and the genesis state they require. +type Scenario interface { + SetupGenesis(GenesisWriter) error + SeedSender(GenesisWriter, common.Address) + BuildTransaction(*ecdsa.PrivateKey, uint64, common.Address) (*ethtypes.Transaction, error) +} + +// NewScenario constructs a backend-neutral scenario. +func NewScenario(kind string, cfg Config) (Scenario, error) { + if err := validateConfig(cfg); err != nil { + return nil, err + } + cfg = cloneConfig(cfg) + switch kind { + case Transfer: + return newTransferScenario(cfg), nil + case ERC20Transfer: + if cfg.ERC20Contract == (common.Address{}) { + return nil, fmt.Errorf("erc20 contract must be non-zero") + } + return newERC20TransferScenario(cfg) + default: + return nil, fmt.Errorf("unsupported offline scenario %q", kind) + } +} + +func validateConfig(cfg Config) error { + switch { + case cfg.ChainID == nil || cfg.ChainID.Sign() <= 0: + return fmt.Errorf("chain ID must be positive") + case cfg.GasPrice == nil || cfg.GasPrice.Sign() < 0: + return fmt.Errorf("gas price must be non-negative") + case cfg.SenderBalance == nil || cfg.SenderBalance.Sign() < 0: + return fmt.Errorf("sender balance must be non-negative") + case cfg.TransferValue == nil || cfg.TransferValue.Sign() < 0: + return fmt.Errorf("transfer value must be non-negative") + case cfg.GasLimit == 0: + return fmt.Errorf("gas limit must be positive") + default: + return nil + } +} + +func cloneConfig(cfg Config) Config { + cfg.ChainID = new(big.Int).Set(cfg.ChainID) + cfg.GasPrice = new(big.Int).Set(cfg.GasPrice) + cfg.SenderBalance = new(big.Int).Set(cfg.SenderBalance) + cfg.TransferValue = new(big.Int).Set(cfg.TransferValue) + return cfg +} diff --git a/generator/offline/scenario_test.go b/generator/offline/scenario_test.go new file mode 100644 index 0000000..367bdd0 --- /dev/null +++ b/generator/offline/scenario_test.go @@ -0,0 +1,126 @@ +package offline + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm/runtime" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/require" +) + +type testGenesis struct { + balances map[common.Address]*big.Int + code map[common.Address][]byte + storage map[common.Address]map[common.Hash]common.Hash +} + +func newTestGenesis() *testGenesis { + return &testGenesis{ + balances: map[common.Address]*big.Int{}, + code: map[common.Address][]byte{}, + storage: map[common.Address]map[common.Hash]common.Hash{}, + } +} + +func (s *testGenesis) SetBalance(address common.Address, balance *big.Int) { + s.balances[address] = new(big.Int).Set(balance) +} + +func (s *testGenesis) SetCode(address common.Address, code []byte) { + s.code[address] = append([]byte(nil), code...) +} + +func (s *testGenesis) SetState(address common.Address, key, value common.Hash) { + if s.storage[address] == nil { + s.storage[address] = map[common.Hash]common.Hash{} + } + s.storage[address][key] = value +} + +func testConfig() Config { + return Config{ + ChainID: big.NewInt(713_715), + GasPrice: new(big.Int), + SenderBalance: new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil), + TransferValue: big.NewInt(17), + GasLimit: 100_000, + ERC20Contract: common.HexToAddress("0x1000"), + } +} + +func TestTransferScenarioBuildsAndSeeds(t *testing.T) { + cfg := testConfig() + scenario, err := NewScenario(Transfer, cfg) + require.NoError(t, err) + state := newTestGenesis() + require.NoError(t, scenario.SetupGenesis(state)) + + key, err := crypto.HexToECDSA("0000000000000000000000000000000000000000000000000000000000000001") + require.NoError(t, err) + sender := crypto.PubkeyToAddress(key.PublicKey) + recipient := common.HexToAddress("0x2000") + scenario.SeedSender(state, sender) + tx, err := scenario.BuildTransaction(key, 3, recipient) + require.NoError(t, err) + + recovered, err := ethtypes.Sender(ethtypes.LatestSignerForChainID(cfg.ChainID), tx) + require.NoError(t, err) + require.Equal(t, sender, recovered) + require.Equal(t, uint64(3), tx.Nonce()) + require.Equal(t, recipient, *tx.To()) + require.Zero(t, cfg.TransferValue.Cmp(tx.Value())) + require.Zero(t, cfg.SenderBalance.Cmp(state.balances[sender])) +} + +func TestERC20TransferScenarioUsesCompiledRuntimeAndBalanceSlot(t *testing.T) { + cfg := testConfig() + scenario, err := NewScenario(ERC20Transfer, cfg) + require.NoError(t, err) + genesis := newTestGenesis() + require.NoError(t, scenario.SetupGenesis(genesis)) + require.NotEmpty(t, genesis.code[cfg.ERC20Contract]) + + key, err := crypto.HexToECDSA("0000000000000000000000000000000000000000000000000000000000000002") + require.NoError(t, err) + sender := crypto.PubkeyToAddress(key.PublicKey) + recipient := common.HexToAddress("0x3000") + scenario.SeedSender(genesis, sender) + require.Equal(t, common.BigToHash(cfg.TransferValue), genesis.storage[cfg.ERC20Contract][ERC20BalanceSlot(sender)]) + + tx, err := scenario.BuildTransaction(key, 0, recipient) + require.NoError(t, err) + require.Equal(t, cfg.ERC20Contract, *tx.To()) + require.Len(t, tx.Data(), 4+32+32) + + db, err := state.New(ethtypes.EmptyRootHash, state.NewDatabaseForTesting()) + require.NoError(t, err) + db.CreateAccount(cfg.ERC20Contract) + db.SetCode(cfg.ERC20Contract, genesis.code[cfg.ERC20Contract]) + db.SetState(cfg.ERC20Contract, ERC20BalanceSlot(sender), common.BigToHash(cfg.TransferValue)) + _, _, err = runtime.Call(cfg.ERC20Contract, tx.Data(), &runtime.Config{ + ChainConfig: params.AllEthashProtocolChanges, + Origin: sender, + BlockNumber: big.NewInt(1), + Time: 1_700_000_000, + GasLimit: cfg.GasLimit, + GasPrice: new(big.Int), + Value: new(big.Int), + BaseFee: new(big.Int), + State: db, + }) + require.NoError(t, err) + require.Equal(t, common.Hash{}, db.GetState(cfg.ERC20Contract, ERC20BalanceSlot(sender))) + require.Equal(t, common.BigToHash(cfg.TransferValue), db.GetState(cfg.ERC20Contract, ERC20BalanceSlot(recipient))) +} + +func TestNewScenarioRejectsInvalidConfig(t *testing.T) { + _, err := NewScenario(Transfer, Config{}) + require.ErrorContains(t, err, "chain ID") + _, err = NewScenario("unknown", testConfig()) + require.ErrorContains(t, err, "unsupported") +} diff --git a/generator/offline/transfer.go b/generator/offline/transfer.go new file mode 100644 index 0000000..bd32af8 --- /dev/null +++ b/generator/offline/transfer.go @@ -0,0 +1,44 @@ +package offline + +import ( + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +type transferScenario struct { + cfg Config + signer ethtypes.Signer +} + +func newTransferScenario(cfg Config) *transferScenario { + return &transferScenario{ + cfg: cfg, + signer: ethtypes.LatestSignerForChainID(cfg.ChainID), + } +} + +func (*transferScenario) SetupGenesis(GenesisWriter) error { + return nil +} + +func (s *transferScenario) SeedSender(state GenesisWriter, sender common.Address) { + state.SetBalance(sender, s.cfg.SenderBalance) +} + +func (s *transferScenario) BuildTransaction(key *ecdsa.PrivateKey, nonce uint64, recipient common.Address) (*ethtypes.Transaction, error) { + if key == nil { + return nil, fmt.Errorf("sender private key is required") + } + tx := ethtypes.NewTx(ðtypes.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(s.cfg.GasPrice), + Gas: s.cfg.GasLimit, + To: &recipient, + Value: new(big.Int).Set(s.cfg.TransferValue), + }) + return ethtypes.SignTx(tx, s.signer, key) +} diff --git a/go.mod b/go.mod index 8aad25a..43de4a2 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/ethereum/go-ethereum v1.16.1 github.com/gogo/protobuf v1.3.2 github.com/google/go-cmp v0.7.0 - github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.22.0 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 @@ -28,6 +27,7 @@ require ( require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.22.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect @@ -41,24 +41,34 @@ require ( github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/ferranbt/fastssz v0.1.2 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f // indirect github.com/prometheus/procfs v0.17.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -81,5 +91,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6457faa..57340ff 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDO github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= @@ -14,6 +16,7 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= @@ -47,6 +50,10 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 h1:+3HCtB74++ClLy8GgjUQYeC8R4ILzVcIe8+5edAJJnE= +github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= github.com/ethereum/go-ethereum v1.16.1 h1:7684NfKCb1+IChudzdKyZJ12l1Tq4ybPZOITiCDXqCk= @@ -73,6 +80,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= @@ -83,12 +92,15 @@ github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQg github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -123,6 +135,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -137,6 +150,7 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= @@ -179,6 +193,8 @@ github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f h1:QQB6S github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw= +github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -281,6 +297,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 455f927b15521f9b99598b95337bd5007dd8155b Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Thu, 6 Aug 2026 11:17:07 +0800 Subject: [PATCH 2/2] validate offline scenario uint256 values --- generator/offline/scenario.go | 6 ++++ generator/offline/scenario_test.go | 47 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/generator/offline/scenario.go b/generator/offline/scenario.go index 19f3f53..d419766 100644 --- a/generator/offline/scenario.go +++ b/generator/offline/scenario.go @@ -66,10 +66,16 @@ func validateConfig(cfg Config) error { return fmt.Errorf("chain ID must be positive") case cfg.GasPrice == nil || cfg.GasPrice.Sign() < 0: return fmt.Errorf("gas price must be non-negative") + case cfg.GasPrice.BitLen() > 256: + return fmt.Errorf("gas price must fit in 256 bits") case cfg.SenderBalance == nil || cfg.SenderBalance.Sign() < 0: return fmt.Errorf("sender balance must be non-negative") + case cfg.SenderBalance.BitLen() > 256: + return fmt.Errorf("sender balance must fit in 256 bits") case cfg.TransferValue == nil || cfg.TransferValue.Sign() < 0: return fmt.Errorf("transfer value must be non-negative") + case cfg.TransferValue.BitLen() > 256: + return fmt.Errorf("transfer value must fit in 256 bits") case cfg.GasLimit == 0: return fmt.Errorf("gas limit must be positive") default: diff --git a/generator/offline/scenario_test.go b/generator/offline/scenario_test.go index 367bdd0..73d1326 100644 --- a/generator/offline/scenario_test.go +++ b/generator/offline/scenario_test.go @@ -124,3 +124,50 @@ func TestNewScenarioRejectsInvalidConfig(t *testing.T) { _, err = NewScenario("unknown", testConfig()) require.ErrorContains(t, err, "unsupported") } + +func TestNewScenarioValidatesUint256Values(t *testing.T) { + maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + cfg := testConfig() + cfg.GasPrice = new(big.Int).Set(maxUint256) + cfg.SenderBalance = new(big.Int).Set(maxUint256) + cfg.TransferValue = new(big.Int).Set(maxUint256) + _, err := NewScenario(Transfer, cfg) + require.NoError(t, err) + + overflow := new(big.Int).Add(maxUint256, big.NewInt(1)) + tests := []struct { + name string + field string + setOverflow func(*Config) + }{ + { + name: "gas price", + field: "gas price", + setOverflow: func(cfg *Config) { + cfg.GasPrice = overflow + }, + }, + { + name: "sender balance", + field: "sender balance", + setOverflow: func(cfg *Config) { + cfg.SenderBalance = overflow + }, + }, + { + name: "transfer value", + field: "transfer value", + setOverflow: func(cfg *Config) { + cfg.TransferValue = overflow + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg := testConfig() + test.setOverflow(&cfg) + _, err := NewScenario(Transfer, cfg) + require.ErrorContains(t, err, test.field+" must fit in 256 bits") + }) + } +}