diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index 9271566692..756a9d3552 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -44,7 +44,7 @@ func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) err
// Deprecated: please use simulated.Backend from package
// github.com/ethereum/go-ethereum/ethclient/simulated instead.
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
- b := simulated.New(alloc, gasLimit)
+ b := simulated.NewBackend(alloc, simulated.WithBlockGasLimit(gasLimit))
return &SimulatedBackend{
Backend: b,
Client: b.Client(),
diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go
index 244eeebdd0..9fd919a295 100644
--- a/accounts/abi/bind/util_test.go
+++ b/accounts/abi/bind/util_test.go
@@ -56,11 +56,10 @@ var waitDeployedTests = map[string]struct {
func TestWaitDeployed(t *testing.T) {
t.Parallel()
for name, test := range waitDeployedTests {
- backend := simulated.New(
+ backend := simulated.NewBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
- 10000000,
)
defer backend.Close()
@@ -102,11 +101,10 @@ func TestWaitDeployed(t *testing.T) {
}
func TestWaitDeployedCornerCases(t *testing.T) {
- backend := simulated.New(
+ backend := simulated.NewBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)},
},
- 10000000,
)
defer backend.Close()
diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go
index 54675b6dd6..6169dde61b 100644
--- a/ethclient/simulated/backend.go
+++ b/ethclient/simulated/backend.go
@@ -34,20 +34,6 @@ import (
"github.com/ethereum/go-ethereum/rpc"
)
-// Backend is a simulated blockchain. You can use it to test your contracts or
-// other code that interacts with the Ethereum chain.
-type Backend struct {
- eth *eth.Ethereum
- beacon *catalyst.SimulatedBeacon
- client simClient
-}
-
-// simClient wraps ethclient. This exists to prevent extracting ethclient.Client
-// from the Client interface returned by Backend.
-type simClient struct {
- *ethclient.Client
-}
-
// Client exposes the methods provided by the Ethereum RPC client.
type Client interface {
ethereum.BlockNumberReader
@@ -66,70 +52,81 @@ type Client interface {
ethereum.ChainIDReader
}
-// New creates a new binding backend using a simulated blockchain
-// for testing purposes.
+// simClient wraps ethclient. This exists to prevent extracting ethclient.Client
+// from the Client interface returned by Backend.
+type simClient struct {
+ *ethclient.Client
+}
+
+// Backend is a simulated blockchain. You can use it to test your contracts or
+// other code that interacts with the Ethereum chain.
+type Backend struct {
+ eth *eth.Ethereum
+ beacon *catalyst.SimulatedBeacon
+ client simClient
+}
+
+// NewBackend creates a new simulated blockchain that can be used as a backend for
+// contract bindings in unit tests.
+//
// A simulated backend always uses chainID 1337.
-func New(alloc core.GenesisAlloc, gasLimit uint64) *Backend {
- // Setup the node object
+func NewBackend(alloc core.GenesisAlloc, options ...func(nodeConf *node.Config, ethConf *ethconfig.Config)) *Backend {
+ // Create the default configurations for the outer node shell and the Ethereum
+ // service to mutate with the options afterwards
nodeConf := node.DefaultConfig
nodeConf.DataDir = ""
nodeConf.P2P = p2p.Config{NoDiscovery: true}
- stack, err := node.New(&nodeConf)
- if err != nil {
- // This should never happen, if it does, please open an issue
- panic(err)
- }
- // Setup ethereum
- genesis := core.Genesis{
+ ethConf := ethconfig.Defaults
+ ethConf.Genesis = &core.Genesis{
Config: params.AllDevChainProtocolChanges,
- GasLimit: gasLimit,
+ GasLimit: ethconfig.Defaults.Miner.GasCeil,
Alloc: alloc,
}
- conf := ethconfig.Defaults
- conf.Genesis = &genesis
- conf.SyncMode = downloader.FullSync
- conf.TxPool.NoLocals = true
- sim, err := newWithNode(stack, &conf, 0)
+ ethConf.SyncMode = downloader.FullSync
+ ethConf.TxPool.NoLocals = true
+
+ for _, option := range options {
+ option(&nodeConf, ðConf)
+ }
+ // Assemble the Ethereum stack to run the chain with
+ stack, err := node.New(&nodeConf)
if err != nil {
- // This should never happen, if it does, please open an issue
- panic(err)
+ panic(err) // this should never happen
+ }
+ sim, err := newWithNode(stack, ðConf, 0)
+ if err != nil {
+ panic(err) // this should never happen
}
return sim
}
-// newWithNode sets up a simulated backend on an existing node
-// this allows users to do persistent simulations.
-// The provided node must not be started and will be started by newWithNode
+// newWithNode sets up a simulated backend on an existing node. The provided node
+// must not be started and will be started by this method.
func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) {
backend, err := eth.New(stack, conf)
if err != nil {
return nil, err
}
-
// Register the filter system
filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
stack.RegisterAPIs([]rpc.API{{
Namespace: "eth",
Service: filters.NewFilterAPI(filterSystem, false),
}})
-
// Start the node
if err := stack.Start(); err != nil {
return nil, err
}
-
// Set up the simulated beacon
beacon, err := catalyst.NewSimulatedBeacon(blockPeriod, backend)
if err != nil {
return nil, err
}
-
// Reorg our chain back to genesis
if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil {
return nil, err
}
-
return &Backend{
eth: backend,
beacon: beacon,
diff --git a/ethclient/simulated/backend_test.go b/ethclient/simulated/backend_test.go
index 16a2acdf4f..a9a8accfea 100644
--- a/ethclient/simulated/backend_test.go
+++ b/ethclient/simulated/backend_test.go
@@ -40,10 +40,10 @@ var (
)
func simTestBackend(testAddr common.Address) *Backend {
- return New(
+ return NewBackend(
core.GenesisAlloc{
testAddr: {Balance: big.NewInt(10000000000000000)},
- }, 10000000,
+ },
)
}
@@ -70,8 +70,8 @@ func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) {
return types.SignTx(tx, types.LatestSignerForChainID(chainid), key)
}
-func TestNewSim(t *testing.T) {
- sim := New(core.GenesisAlloc{}, 30_000_000)
+func TestNewBackend(t *testing.T) {
+ sim := NewBackend(core.GenesisAlloc{})
defer sim.Close()
client := sim.Client()
@@ -94,7 +94,7 @@ func TestNewSim(t *testing.T) {
}
func TestAdjustTime(t *testing.T) {
- sim := New(core.GenesisAlloc{}, 10_000_000)
+ sim := NewBackend(core.GenesisAlloc{})
defer sim.Close()
client := sim.Client()
diff --git a/ethclient/simulated/options.go b/ethclient/simulated/options.go
new file mode 100644
index 0000000000..1b2f4c090d
--- /dev/null
+++ b/ethclient/simulated/options.go
@@ -0,0 +1,39 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package simulated
+
+import (
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
+ "github.com/ethereum/go-ethereum/node"
+)
+
+// WithBlockGasLimit configures the simulated backend to target a specific gas limit
+// when producing blocks.
+func WithBlockGasLimit(gaslimit uint64) func(nodeConf *node.Config, ethConf *ethconfig.Config) {
+ return func(nodeConf *node.Config, ethConf *ethconfig.Config) {
+ ethConf.Genesis.GasLimit = gaslimit
+ ethConf.Miner.GasCeil = gaslimit
+ }
+}
+
+// WithCallGasLimit configures the simulated backend to cap eth_calls to a specific
+// gas limit when running client operations.
+func WithCallGasLimit(gaslimit uint64) func(nodeConf *node.Config, ethConf *ethconfig.Config) {
+ return func(nodeConf *node.Config, ethConf *ethconfig.Config) {
+ ethConf.RPCGasCap = gaslimit
+ }
+}
diff --git a/ethclient/simulated/options_test.go b/ethclient/simulated/options_test.go
new file mode 100644
index 0000000000..d9ff3b428a
--- /dev/null
+++ b/ethclient/simulated/options_test.go
@@ -0,0 +1,73 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package simulated
+
+import (
+ "context"
+ "math/big"
+ "strings"
+ "testing"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// Tests that the simulator starts with the initial gas limit in the genesis block,
+// and that it keeps the same target value.
+func TestWithBlockGasLimitOption(t *testing.T) {
+ // Construct a simulator, targeting a different gas limit
+ sim := NewBackend(core.GenesisAlloc{}, WithBlockGasLimit(12_345_678))
+ defer sim.Close()
+
+ client := sim.Client()
+ genesis, err := client.BlockByNumber(context.Background(), big.NewInt(0))
+ if err != nil {
+ t.Fatalf("failed to retrieve genesis block: %v", err)
+ }
+ if genesis.GasLimit() != 12_345_678 {
+ t.Errorf("genesis gas limit mismatch: have %v, want %v", genesis.GasLimit(), 12_345_678)
+ }
+ // Produce a number of blocks and verify the locked in gas target
+ sim.Commit()
+ head, err := client.BlockByNumber(context.Background(), big.NewInt(1))
+ if err != nil {
+ t.Fatalf("failed to retrieve head block: %v", err)
+ }
+ if head.GasLimit() != 12_345_678 {
+ t.Errorf("head gas limit mismatch: have %v, want %v", head.GasLimit(), 12_345_678)
+ }
+}
+
+// Tests that the simulator honors the RPC call caps set by the options.
+func TestWithCallGasLimitOption(t *testing.T) {
+ // Construct a simulator, targeting a different gas limit
+ sim := NewBackend(core.GenesisAlloc{
+ testAddr: {Balance: big.NewInt(10000000000000000)},
+ }, WithCallGasLimit(params.TxGas-1))
+ defer sim.Close()
+
+ client := sim.Client()
+ _, err := client.CallContract(context.Background(), ethereum.CallMsg{
+ From: testAddr,
+ To: &testAddr,
+ Gas: 21000,
+ }, nil)
+ if !strings.Contains(err.Error(), core.ErrIntrinsicGas.Error()) {
+ t.Fatalf("error mismatch: have %v, want %v", err, core.ErrIntrinsicGas)
+ }
+}