From 171b798c2f860f1f27971e07551809e75aedc4a0 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Tue, 4 Jun 2024 11:06:29 -0400 Subject: [PATCH 01/22] ChainWriter EVM GetFeeComponents implementation --- core/services/relay/evm/chain_writer.go | 45 +++++++++++++++++++++++-- core/services/relay/evm/write_target.go | 2 +- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index fa62297efce..1e5ac56181e 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -3,6 +3,8 @@ package evm import ( "context" "fmt" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "math/big" "strings" @@ -30,11 +32,15 @@ type ChainWriterService interface { // Compile-time assertion that chainWriter implements the ChainWriterService interface. var _ ChainWriterService = (*chainWriter)(nil) -func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, config types.ChainWriterConfig) (ChainWriterService, error) { +func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, estimator gas.EvmFeeEstimator, maxGasPrice *big.Int, config types.ChainWriterConfig) (ChainWriterService, error) { w := chainWriter{ logger: logger, client: client, txm: txm, + ge: ExecGasEstimator{ + estimator: estimator, + maxGasPrice: maxGasPrice, + }, sendStrategy: txmgr.NewSendEveryStrategy(), contracts: config.Contracts, @@ -63,6 +69,7 @@ type chainWriter struct { logger logger.Logger client evmclient.Client txm evmtxmgr.TxManager + ge ExecGasEstimator sendStrategy txmgrtypes.TxStrategy contracts map[string]*types.ContractConfig @@ -71,6 +78,11 @@ type chainWriter struct { encoder commontypes.Encoder } +type ExecGasEstimator struct { + estimator gas.EvmFeeEstimator + maxGasPrice *big.Int +} + // SubmitTransaction ... // // Note: The codec that ChainWriter uses to encode the parameters for the contract ABI cannot handle @@ -156,7 +168,36 @@ func (w *chainWriter) GetTransactionStatus(ctx context.Context, transactionID uu } func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainFeeComponents, error) { - return nil, fmt.Errorf("not implemented") + if w.ge.estimator == nil { + return nil, fmt.Errorf("not implemented") + } + + gasPriceWei, _, err := w.ge.estimator.GetFee(ctx, nil, 0, assets.NewWei(w.ge.maxGasPrice)) + + // Use legacy if no dynamic is available. + gasPrice := gasPriceWei.Legacy.ToInt() + if gasPriceWei.DynamicFeeCap != nil { + gasPrice = gasPriceWei.DynamicFeeCap.ToInt() + } + if gasPrice == nil { + return nil, fmt.Errorf("missing gas price %+v", gasPriceWei) + } + l1Oracle := w.ge.estimator.L1Oracle() + if l1Oracle == nil { + return &commontypes.ChainFeeComponents{ + ExecutionFee: *gasPrice, + DataAvailabilityFee: *big.NewInt(0), + }, nil + } + l1OracleFee, err := l1Oracle.GasPrice(ctx) + if err != nil { + return nil, err + } + + return &commontypes.ChainFeeComponents{ + ExecutionFee: *gasPrice, + DataAvailabilityFee: *big.NewInt(l1OracleFee.Int64()), + }, nil } func (w *chainWriter) Close() error { diff --git a/core/services/relay/evm/write_target.go b/core/services/relay/evm/write_target.go index e5fd1385609..416d4c2676a 100644 --- a/core/services/relay/evm/write_target.go +++ b/core/services/relay/evm/write_target.go @@ -69,7 +69,7 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain }, }, } - cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), chainWriterConfig) + cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), nil, chainWriterConfig) if err != nil { return nil, err } From 7ea570cb62b238f6c799b1601615ec8d42607e0f Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Tue, 4 Jun 2024 11:09:01 -0400 Subject: [PATCH 02/22] Fixed Write Target CW initialization --- core/services/relay/evm/write_target.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/services/relay/evm/write_target.go b/core/services/relay/evm/write_target.go index 3bca4c3a8fc..0b26d2aa542 100644 --- a/core/services/relay/evm/write_target.go +++ b/core/services/relay/evm/write_target.go @@ -69,7 +69,8 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain }, }, } - cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), nil, chainWriterConfig) + + cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), nil, nil, chainWriterConfig) if err != nil { return nil, err } From cc2542b85ac5b545ae0e21a4fe22100c1540bbac Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Tue, 4 Jun 2024 11:15:47 -0400 Subject: [PATCH 03/22] added changeset --- .changeset/fuzzy-frogs-live.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fuzzy-frogs-live.md diff --git a/.changeset/fuzzy-frogs-live.md b/.changeset/fuzzy-frogs-live.md new file mode 100644 index 00000000000..58a9ffd2288 --- /dev/null +++ b/.changeset/fuzzy-frogs-live.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +#added EVM implementation of GetFeeComponents function for ChainWriter From 857c7da8f403e15c7f72d2bf5c97c84477605e48 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Wed, 5 Jun 2024 16:19:07 -0400 Subject: [PATCH 04/22] Added ChainWriter tests --- core/services/relay/evm/chain_writer_test.go | 156 +++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 core/services/relay/evm/chain_writer_test.go diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go new file mode 100644 index 00000000000..dced2b49039 --- /dev/null +++ b/core/services/relay/evm/chain_writer_test.go @@ -0,0 +1,156 @@ +package evm + +import ( + "fmt" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" + rollupmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" + txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + relayevmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "math/big" + "testing" +) + +func TestChainWriter(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := testutils.Context(t) + + txm := txmmocks.NewMockEvmTxManager(t) + client := evmclimocks.NewClient(t) + ge := gasmocks.NewEvmFeeEstimator(t) + l1Oracle := rollupmocks.NewL1Oracle(t) + + maxGasPrice := big.NewInt(1000000) + + chainWriterConfig := newBaseChainWriterConfig() + cw, err := NewChainWriterService(lggr, client, txm, ge, maxGasPrice, chainWriterConfig) + + require.NoError(t, err) + + t.Run("Initialization", func(t *testing.T) { + t.Run("Fails with invalid ABI", func(t *testing.T) { + baseConfig := newBaseChainWriterConfig() + invalidAbiConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { + cfg.Contracts["forwarder"].ContractABI = "" + }) + _, err = NewChainWriterService(lggr, client, txm, ge, maxGasPrice, invalidAbiConfig) + require.Error(t, err) + }) + + t.Run("Fails with invalid method names", func(t *testing.T) { + baseConfig := newBaseChainWriterConfig() + invalidMethodNameConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { + cfg.Contracts["forwarder"].Configs["report"].ChainSpecificName = "" + }) + _, err = NewChainWriterService(lggr, client, txm, ge, maxGasPrice, invalidMethodNameConfig) + require.Error(t, err) + }) + + t.Run("Fails with invalid codecs", func(t *testing.T) { + // TODO: implement + }) + + }) + + t.Run("SubmitTransaction", func(t *testing.T) { + // TODO: implement + }) + + t.Run("GetFeeComponents", func(t *testing.T) { + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: assets.NewWei(big.NewInt(1000000001)), + DynamicFeeCap: assets.NewWei(big.NewInt(1000000002)), + DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), + }, uint64(0), nil).Twice() + + l1Oracle.On("GasPrice", mock.Anything).Return(assets.NewWei(big.NewInt(1000000004)), nil).Once() + ge.On("L1Oracle", mock.Anything).Return(l1Oracle).Once() + var feeComponents *types.ChainFeeComponents + t.Run("Returns valid FeeComponents", func(t *testing.T) { + feeComponents, err = cw.GetFeeComponents(ctx) + require.NoError(t, err) + assert.Equal(t, big.NewInt(1000000002), &feeComponents.ExecutionFee) + assert.Equal(t, big.NewInt(1000000004), &feeComponents.DataAvailabilityFee) + }) + + ge.On("L1Oracle", mock.Anything).Return(nil).Twice() + + t.Run("Returns valid FeeComponents with no L1Oracle", func(t *testing.T) { + feeComponents, err = cw.GetFeeComponents(ctx) + require.NoError(t, err) + assert.Equal(t, big.NewInt(1000000002), &feeComponents.ExecutionFee) + assert.Equal(t, big.NewInt(0), &feeComponents.DataAvailabilityFee) + }) + + t.Run("Returns Legacy Fee in absence of Dynamic Fee", func(t *testing.T) { + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: assets.NewWei(big.NewInt(1000000001)), + DynamicFeeCap: nil, + DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), + }, uint64(0), nil).Once() + feeComponents, err = cw.GetFeeComponents(ctx) + require.NoError(t, err) + assert.Equal(t, big.NewInt(1000000001), &feeComponents.ExecutionFee) + assert.Equal(t, big.NewInt(0), &feeComponents.DataAvailabilityFee) + }) + + t.Run("Fails when neither legacy or dynamic fee is available", func(t *testing.T) { + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: nil, + DynamicFeeCap: nil, + DynamicTipCap: nil, + }, uint64(0), nil).Once() + + _, err = cw.GetFeeComponents(ctx) + require.Error(t, err) + }) + + t.Run("Fails when L1Oracle returns error", func(t *testing.T) { + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: assets.NewWei(big.NewInt(1000000001)), + DynamicFeeCap: assets.NewWei(big.NewInt(1000000002)), + DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), + }, uint64(0), nil).Once() + ge.On("L1Oracle", mock.Anything).Return(l1Oracle).Once() + + expectedErr := fmt.Errorf("l1Oracle error") + l1Oracle.On("GasPrice", mock.Anything).Return(nil, expectedErr).Once() + _, err = cw.GetFeeComponents(ctx) + require.Equal(t, expectedErr, err) + }) + }) +} + +// Helper functions to remove redundant creation of configs +func newBaseChainWriterConfig() relayevmtypes.ChainWriterConfig { + return relayevmtypes.ChainWriterConfig{ + Contracts: map[string]*relayevmtypes.ContractConfig{ + "forwarder": { + ContractABI: forwarder.KeystoneForwarderABI, + Configs: map[string]*relayevmtypes.ChainWriterDefinition{ + "report": { + ChainSpecificName: "report", + Checker: "simulate", + FromAddress: testutils.NewAddress(), + GasLimit: 200_000, + }, + }, + }, + }, + } +} + +func modifyChainWriterConfig(baseConfig relayevmtypes.ChainWriterConfig, modifyFn func(*relayevmtypes.ChainWriterConfig)) relayevmtypes.ChainWriterConfig { + modifiedConfig := baseConfig + modifyFn(&modifiedConfig) + return modifiedConfig +} From 86b5f8013393e1ea608d875229d1cfd6f3aa3206 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Wed, 5 Jun 2024 16:36:33 -0400 Subject: [PATCH 05/22] linting fix --- core/services/relay/evm/chain_writer.go | 9 +++++--- core/services/relay/evm/chain_writer_test.go | 24 +++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 1e5ac56181e..14a48db0656 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -3,11 +3,12 @@ package evm import ( "context" "fmt" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "math/big" "strings" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" @@ -173,7 +174,9 @@ func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainF } gasPriceWei, _, err := w.ge.estimator.GetFee(ctx, nil, 0, assets.NewWei(w.ge.maxGasPrice)) - + if err != nil { + return nil, err + } // Use legacy if no dynamic is available. gasPrice := gasPriceWei.Legacy.ToInt() if gasPriceWei.DynamicFeeCap != nil { diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go index dced2b49039..9243ab7adc8 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -2,6 +2,13 @@ package evm import ( "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" @@ -13,11 +20,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" relayevmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "math/big" - "testing" ) func TestChainWriter(t *testing.T) { @@ -58,7 +60,6 @@ func TestChainWriter(t *testing.T) { t.Run("Fails with invalid codecs", func(t *testing.T) { // TODO: implement }) - }) t.Run("SubmitTransaction", func(t *testing.T) { @@ -114,6 +115,17 @@ func TestChainWriter(t *testing.T) { require.Error(t, err) }) + t.Run("Fails when GetFee returns an error", func(t *testing.T) { + expectedErr := fmt.Errorf("GetFee error") + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: nil, + DynamicFeeCap: nil, + DynamicTipCap: nil, + }, uint64(0), expectedErr).Once() + _, err = cw.GetFeeComponents(ctx) + require.Equal(t, expectedErr, err) + }) + t.Run("Fails when L1Oracle returns error", func(t *testing.T) { ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ Legacy: assets.NewWei(big.NewInt(1000000001)), From cef576dd2db96e27b866500fec5368b40e1ccddf Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Thu, 6 Jun 2024 13:01:59 -0400 Subject: [PATCH 06/22] Updated comment --- core/services/relay/evm/chain_writer_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go index 9243ab7adc8..66981c05c0f 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -147,6 +147,7 @@ func newBaseChainWriterConfig() relayevmtypes.ChainWriterConfig { return relayevmtypes.ChainWriterConfig{ Contracts: map[string]*relayevmtypes.ContractConfig{ "forwarder": { + // TODO: Use generic ABI / test contract rather than a keystone specific one ContractABI: forwarder.KeystoneForwarderABI, Configs: map[string]*relayevmtypes.ChainWriterDefinition{ "report": { From f0d2ca52ead9ba3136bafc8e2c5cf7915f858ee3 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Mon, 10 Jun 2024 12:52:16 -0400 Subject: [PATCH 07/22] Moved MaxGasPrice to CW Config --- core/services/relay/evm/chain_writer.go | 4 ++-- core/services/relay/evm/chain_writer_test.go | 9 ++++----- core/services/relay/evm/types/types.go | 2 ++ core/services/relay/evm/write_target.go | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 14a48db0656..6751707163b 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -33,14 +33,14 @@ type ChainWriterService interface { // Compile-time assertion that chainWriter implements the ChainWriterService interface. var _ ChainWriterService = (*chainWriter)(nil) -func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, estimator gas.EvmFeeEstimator, maxGasPrice *big.Int, config types.ChainWriterConfig) (ChainWriterService, error) { +func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, estimator gas.EvmFeeEstimator, config types.ChainWriterConfig) (ChainWriterService, error) { w := chainWriter{ logger: logger, client: client, txm: txm, ge: ExecGasEstimator{ estimator: estimator, - maxGasPrice: maxGasPrice, + maxGasPrice: config.MaxGasPrice, }, sendStrategy: txmgr.NewSendEveryStrategy(), diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go index 66981c05c0f..5e57599c94d 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -31,10 +31,8 @@ func TestChainWriter(t *testing.T) { ge := gasmocks.NewEvmFeeEstimator(t) l1Oracle := rollupmocks.NewL1Oracle(t) - maxGasPrice := big.NewInt(1000000) - chainWriterConfig := newBaseChainWriterConfig() - cw, err := NewChainWriterService(lggr, client, txm, ge, maxGasPrice, chainWriterConfig) + cw, err := NewChainWriterService(lggr, client, txm, ge, chainWriterConfig) require.NoError(t, err) @@ -44,7 +42,7 @@ func TestChainWriter(t *testing.T) { invalidAbiConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { cfg.Contracts["forwarder"].ContractABI = "" }) - _, err = NewChainWriterService(lggr, client, txm, ge, maxGasPrice, invalidAbiConfig) + _, err = NewChainWriterService(lggr, client, txm, ge, invalidAbiConfig) require.Error(t, err) }) @@ -53,7 +51,7 @@ func TestChainWriter(t *testing.T) { invalidMethodNameConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { cfg.Contracts["forwarder"].Configs["report"].ChainSpecificName = "" }) - _, err = NewChainWriterService(lggr, client, txm, ge, maxGasPrice, invalidMethodNameConfig) + _, err = NewChainWriterService(lggr, client, txm, ge, invalidMethodNameConfig) require.Error(t, err) }) @@ -159,6 +157,7 @@ func newBaseChainWriterConfig() relayevmtypes.ChainWriterConfig { }, }, }, + MaxGasPrice: big.NewInt(1000000), } } diff --git a/core/services/relay/evm/types/types.go b/core/services/relay/evm/types/types.go index a4c7e69a2fa..fe02878c5e9 100644 --- a/core/services/relay/evm/types/types.go +++ b/core/services/relay/evm/types/types.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + mathBig "math/big" "github.com/ethereum/go-ethereum/common" "github.com/lib/pq" @@ -25,6 +26,7 @@ import ( type ChainWriterConfig struct { Contracts map[string]*ContractConfig SendStrategy txmgrtypes.TxStrategy + MaxGasPrice *mathBig.Int } type ContractConfig struct { diff --git a/core/services/relay/evm/write_target.go b/core/services/relay/evm/write_target.go index 0b26d2aa542..fcc2155f877 100644 --- a/core/services/relay/evm/write_target.go +++ b/core/services/relay/evm/write_target.go @@ -70,7 +70,7 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain }, } - cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), nil, nil, chainWriterConfig) + cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), nil, chainWriterConfig) if err != nil { return nil, err } From 094c97f34a6b0f1656cc9907cf91e1d9154a4b4b Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Mon, 10 Jun 2024 12:55:16 -0400 Subject: [PATCH 08/22] Changed ExecGasEstimator to CWGasEstimator --- core/services/relay/evm/chain_writer.go | 6 +++--- core/services/relay/evm/chain_writer_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 6751707163b..959f8adaeba 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -38,7 +38,7 @@ func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm ev logger: logger, client: client, txm: txm, - ge: ExecGasEstimator{ + ge: CWGasEstimator{ estimator: estimator, maxGasPrice: config.MaxGasPrice, }, @@ -70,7 +70,7 @@ type chainWriter struct { logger logger.Logger client evmclient.Client txm evmtxmgr.TxManager - ge ExecGasEstimator + ge CWGasEstimator sendStrategy txmgrtypes.TxStrategy contracts map[string]*types.ContractConfig @@ -79,7 +79,7 @@ type chainWriter struct { encoder commontypes.Encoder } -type ExecGasEstimator struct { +type CWGasEstimator struct { estimator gas.EvmFeeEstimator maxGasPrice *big.Int } diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go index 5e57599c94d..85ba500dade 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -157,7 +157,7 @@ func newBaseChainWriterConfig() relayevmtypes.ChainWriterConfig { }, }, }, - MaxGasPrice: big.NewInt(1000000), + MaxGasPrice: big.NewInt(1000000000000), } } From d0e2d72e2c1b17ce608c88d553162e575860aebf Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Mon, 10 Jun 2024 15:52:11 -0400 Subject: [PATCH 09/22] removed tests from this PR --- core/services/relay/evm/chain_writer_test.go | 168 ------------------- 1 file changed, 168 deletions(-) delete mode 100644 core/services/relay/evm/chain_writer_test.go diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go deleted file mode 100644 index 85ba500dade..00000000000 --- a/core/services/relay/evm/chain_writer_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package evm - -import ( - "fmt" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" - evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" - gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" - rollupmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" - txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/logger" - relayevmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" -) - -func TestChainWriter(t *testing.T) { - lggr := logger.TestLogger(t) - ctx := testutils.Context(t) - - txm := txmmocks.NewMockEvmTxManager(t) - client := evmclimocks.NewClient(t) - ge := gasmocks.NewEvmFeeEstimator(t) - l1Oracle := rollupmocks.NewL1Oracle(t) - - chainWriterConfig := newBaseChainWriterConfig() - cw, err := NewChainWriterService(lggr, client, txm, ge, chainWriterConfig) - - require.NoError(t, err) - - t.Run("Initialization", func(t *testing.T) { - t.Run("Fails with invalid ABI", func(t *testing.T) { - baseConfig := newBaseChainWriterConfig() - invalidAbiConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { - cfg.Contracts["forwarder"].ContractABI = "" - }) - _, err = NewChainWriterService(lggr, client, txm, ge, invalidAbiConfig) - require.Error(t, err) - }) - - t.Run("Fails with invalid method names", func(t *testing.T) { - baseConfig := newBaseChainWriterConfig() - invalidMethodNameConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { - cfg.Contracts["forwarder"].Configs["report"].ChainSpecificName = "" - }) - _, err = NewChainWriterService(lggr, client, txm, ge, invalidMethodNameConfig) - require.Error(t, err) - }) - - t.Run("Fails with invalid codecs", func(t *testing.T) { - // TODO: implement - }) - }) - - t.Run("SubmitTransaction", func(t *testing.T) { - // TODO: implement - }) - - t.Run("GetFeeComponents", func(t *testing.T) { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ - Legacy: assets.NewWei(big.NewInt(1000000001)), - DynamicFeeCap: assets.NewWei(big.NewInt(1000000002)), - DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), - }, uint64(0), nil).Twice() - - l1Oracle.On("GasPrice", mock.Anything).Return(assets.NewWei(big.NewInt(1000000004)), nil).Once() - ge.On("L1Oracle", mock.Anything).Return(l1Oracle).Once() - var feeComponents *types.ChainFeeComponents - t.Run("Returns valid FeeComponents", func(t *testing.T) { - feeComponents, err = cw.GetFeeComponents(ctx) - require.NoError(t, err) - assert.Equal(t, big.NewInt(1000000002), &feeComponents.ExecutionFee) - assert.Equal(t, big.NewInt(1000000004), &feeComponents.DataAvailabilityFee) - }) - - ge.On("L1Oracle", mock.Anything).Return(nil).Twice() - - t.Run("Returns valid FeeComponents with no L1Oracle", func(t *testing.T) { - feeComponents, err = cw.GetFeeComponents(ctx) - require.NoError(t, err) - assert.Equal(t, big.NewInt(1000000002), &feeComponents.ExecutionFee) - assert.Equal(t, big.NewInt(0), &feeComponents.DataAvailabilityFee) - }) - - t.Run("Returns Legacy Fee in absence of Dynamic Fee", func(t *testing.T) { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ - Legacy: assets.NewWei(big.NewInt(1000000001)), - DynamicFeeCap: nil, - DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), - }, uint64(0), nil).Once() - feeComponents, err = cw.GetFeeComponents(ctx) - require.NoError(t, err) - assert.Equal(t, big.NewInt(1000000001), &feeComponents.ExecutionFee) - assert.Equal(t, big.NewInt(0), &feeComponents.DataAvailabilityFee) - }) - - t.Run("Fails when neither legacy or dynamic fee is available", func(t *testing.T) { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ - Legacy: nil, - DynamicFeeCap: nil, - DynamicTipCap: nil, - }, uint64(0), nil).Once() - - _, err = cw.GetFeeComponents(ctx) - require.Error(t, err) - }) - - t.Run("Fails when GetFee returns an error", func(t *testing.T) { - expectedErr := fmt.Errorf("GetFee error") - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ - Legacy: nil, - DynamicFeeCap: nil, - DynamicTipCap: nil, - }, uint64(0), expectedErr).Once() - _, err = cw.GetFeeComponents(ctx) - require.Equal(t, expectedErr, err) - }) - - t.Run("Fails when L1Oracle returns error", func(t *testing.T) { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ - Legacy: assets.NewWei(big.NewInt(1000000001)), - DynamicFeeCap: assets.NewWei(big.NewInt(1000000002)), - DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), - }, uint64(0), nil).Once() - ge.On("L1Oracle", mock.Anything).Return(l1Oracle).Once() - - expectedErr := fmt.Errorf("l1Oracle error") - l1Oracle.On("GasPrice", mock.Anything).Return(nil, expectedErr).Once() - _, err = cw.GetFeeComponents(ctx) - require.Equal(t, expectedErr, err) - }) - }) -} - -// Helper functions to remove redundant creation of configs -func newBaseChainWriterConfig() relayevmtypes.ChainWriterConfig { - return relayevmtypes.ChainWriterConfig{ - Contracts: map[string]*relayevmtypes.ContractConfig{ - "forwarder": { - // TODO: Use generic ABI / test contract rather than a keystone specific one - ContractABI: forwarder.KeystoneForwarderABI, - Configs: map[string]*relayevmtypes.ChainWriterDefinition{ - "report": { - ChainSpecificName: "report", - Checker: "simulate", - FromAddress: testutils.NewAddress(), - GasLimit: 200_000, - }, - }, - }, - }, - MaxGasPrice: big.NewInt(1000000000000), - } -} - -func modifyChainWriterConfig(baseConfig relayevmtypes.ChainWriterConfig, modifyFn func(*relayevmtypes.ChainWriterConfig)) relayevmtypes.ChainWriterConfig { - modifiedConfig := baseConfig - modifyFn(&modifiedConfig) - return modifiedConfig -} From ce9968e0edd4d762c1d97ad8c202bd04dc2ac8c9 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Mon, 10 Jun 2024 20:21:28 -0400 Subject: [PATCH 10/22] Added comments and updated cw struct --- core/services/relay/evm/chain_writer.go | 37 ++++++++++++------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 959f8adaeba..0581c83a710 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -35,13 +35,11 @@ var _ ChainWriterService = (*chainWriter)(nil) func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, estimator gas.EvmFeeEstimator, config types.ChainWriterConfig) (ChainWriterService, error) { w := chainWriter{ - logger: logger, - client: client, - txm: txm, - ge: CWGasEstimator{ - estimator: estimator, - maxGasPrice: config.MaxGasPrice, - }, + logger: logger, + client: client, + txm: txm, + ge: estimator, + maxGasPrice: config.MaxGasPrice, sendStrategy: txmgr.NewSendEveryStrategy(), contracts: config.Contracts, @@ -67,10 +65,11 @@ func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm ev type chainWriter struct { commonservices.StateMachine - logger logger.Logger - client evmclient.Client - txm evmtxmgr.TxManager - ge CWGasEstimator + logger logger.Logger + client evmclient.Client + txm evmtxmgr.TxManager + ge gas.EvmFeeEstimator + maxGasPrice *big.Int sendStrategy txmgrtypes.TxStrategy contracts map[string]*types.ContractConfig @@ -79,11 +78,6 @@ type chainWriter struct { encoder commontypes.Encoder } -type CWGasEstimator struct { - estimator gas.EvmFeeEstimator - maxGasPrice *big.Int -} - // SubmitTransaction ... // // Note: The codec that ChainWriter uses to encode the parameters for the contract ABI cannot handle @@ -168,12 +162,17 @@ func (w *chainWriter) GetTransactionStatus(ctx context.Context, transactionID uu return commontypes.Unknown, fmt.Errorf("not implemented") } +// SubmitTransaction +// +// Returns the execution and data availability (L1Oracle) fees for the chain. +// Dynamic fees (introduced in EIP-1559) include a fee cap and a tip cap. If the dyanmic fee is not available, +// (if the chain doesn't support dynamic TXs) the legacy GasPrice is used. func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainFeeComponents, error) { - if w.ge.estimator == nil { + if w.ge == nil { return nil, fmt.Errorf("not implemented") } - gasPriceWei, _, err := w.ge.estimator.GetFee(ctx, nil, 0, assets.NewWei(w.ge.maxGasPrice)) + gasPriceWei, _, err := w.ge.GetFee(ctx, nil, 0, assets.NewWei(w.maxGasPrice)) if err != nil { return nil, err } @@ -185,7 +184,7 @@ func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainF if gasPrice == nil { return nil, fmt.Errorf("missing gas price %+v", gasPriceWei) } - l1Oracle := w.ge.estimator.L1Oracle() + l1Oracle := w.ge.L1Oracle() if l1Oracle == nil { return &commontypes.ChainFeeComponents{ ExecutionFee: *gasPrice, From 1e8b94b831e3f07134f77653c14234e460358eac Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Tue, 11 Jun 2024 09:04:39 -0400 Subject: [PATCH 11/22] renamed gasPriceWei to evmFee --- core/services/relay/evm/chain_writer.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 0581c83a710..d4b16d54c2c 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -172,17 +172,17 @@ func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainF return nil, fmt.Errorf("not implemented") } - gasPriceWei, _, err := w.ge.GetFee(ctx, nil, 0, assets.NewWei(w.maxGasPrice)) + fee, _, err := w.ge.GetFee(ctx, nil, 0, assets.NewWei(w.maxGasPrice)) if err != nil { return nil, err } // Use legacy if no dynamic is available. - gasPrice := gasPriceWei.Legacy.ToInt() - if gasPriceWei.DynamicFeeCap != nil { - gasPrice = gasPriceWei.DynamicFeeCap.ToInt() + gasPrice := fee.Legacy.ToInt() + if fee.DynamicFeeCap != nil { + gasPrice = fee.DynamicFeeCap.ToInt() } if gasPrice == nil { - return nil, fmt.Errorf("missing gas price %+v", gasPriceWei) + return nil, fmt.Errorf("missing gas price %+v", fee) } l1Oracle := w.ge.L1Oracle() if l1Oracle == nil { From 684185a870cb017f33ffa54a9560e39ae9b9efa6 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Tue, 11 Jun 2024 10:45:12 -0400 Subject: [PATCH 12/22] Added more specific error for gasPrice == nil --- core/services/relay/evm/chain_writer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index d4b16d54c2c..5e719ec738b 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -182,7 +182,7 @@ func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainF gasPrice = fee.DynamicFeeCap.ToInt() } if gasPrice == nil { - return nil, fmt.Errorf("missing gas price %+v", fee) + return nil, fmt.Errorf("Dynamic fee and legacy gas price missing %+v", fee) } l1Oracle := w.ge.L1Oracle() if l1Oracle == nil { From efa22e794a5d5b27816bb1b43cd4abc6510f95f0 Mon Sep 17 00:00:00 2001 From: Silas Lenihan <32529249+silaslenihan@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:03:07 -0400 Subject: [PATCH 13/22] Update core/services/relay/evm/chain_writer.go Co-authored-by: Jordan Krage --- core/services/relay/evm/chain_writer.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 5e719ec738b..91bed6c5083 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -162,9 +162,7 @@ func (w *chainWriter) GetTransactionStatus(ctx context.Context, transactionID uu return commontypes.Unknown, fmt.Errorf("not implemented") } -// SubmitTransaction -// -// Returns the execution and data availability (L1Oracle) fees for the chain. +// GetFeeComponents the execution and data availability (L1Oracle) fees for the chain. // Dynamic fees (introduced in EIP-1559) include a fee cap and a tip cap. If the dyanmic fee is not available, // (if the chain doesn't support dynamic TXs) the legacy GasPrice is used. func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainFeeComponents, error) { From 02f0e0a67a388ab6149d38ed71021ade23e2d4a3 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Wed, 12 Jun 2024 10:40:53 -0400 Subject: [PATCH 14/22] Added tests back in --- .../evm/gas/{helpers_test.go => helpers.go} | 0 core/services/relay/evm/chain_writer_test.go | 184 ++++++++++++++++++ 2 files changed, 184 insertions(+) rename core/chains/evm/gas/{helpers_test.go => helpers.go} (100%) create mode 100644 core/services/relay/evm/chain_writer_test.go diff --git a/core/chains/evm/gas/helpers_test.go b/core/chains/evm/gas/helpers.go similarity index 100% rename from core/chains/evm/gas/helpers_test.go rename to core/chains/evm/gas/helpers.go diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go new file mode 100644 index 00000000000..8871fc9ecc6 --- /dev/null +++ b/core/services/relay/evm/chain_writer_test.go @@ -0,0 +1,184 @@ +package evm + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + commonLggr "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" + rollupmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" + txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + relayevmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" +) + +type MockGasEstimatorConfig struct { + EIP1559DynamicFeesF bool + BumpPercentF uint16 + BumpThresholdF uint64 + BumpMinF *assets.Wei + LimitMultiplierF float32 + TipCapDefaultF *assets.Wei + TipCapMinF *assets.Wei + PriceMaxF *assets.Wei + PriceMinF *assets.Wei + PriceDefaultF *assets.Wei + FeeCapDefaultF *assets.Wei + LimitMaxF uint64 + ModeF string +} + +func TestChainWriter(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := testutils.Context(t) + + txm := txmmocks.NewMockEvmTxManager(t) + client := evmclimocks.NewClient(t) + ge := gasmocks.NewEvmEstimator(t) + + geCfg := gas.NewMockGasConfig() + + getEst := func(commonLggr.Logger) gas.EvmEstimator { return ge } + + feeEstimator := gas.NewEvmFeeEstimator(commonLggr.Test(t), getEst, true, geCfg) + l1Oracle := rollupmocks.NewL1Oracle(t) + + chainWriterConfig := newBaseChainWriterConfig() + cw, err := NewChainWriterService(lggr, client, txm, feeEstimator, chainWriterConfig) + + require.NoError(t, err) + + t.Run("Initialization", func(t *testing.T) { + t.Run("Fails with invalid ABI", func(t *testing.T) { + baseConfig := newBaseChainWriterConfig() + invalidAbiConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { + cfg.Contracts["forwarder"].ContractABI = "" + }) + _, err = NewChainWriterService(lggr, client, txm, feeEstimator, invalidAbiConfig) + require.Error(t, err) + }) + + t.Run("Fails with invalid method names", func(t *testing.T) { + baseConfig := newBaseChainWriterConfig() + invalidMethodNameConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { + cfg.Contracts["forwarder"].Configs["report"].ChainSpecificName = "" + }) + _, err = NewChainWriterService(lggr, client, txm, feeEstimator, invalidMethodNameConfig) + require.Error(t, err) + }) + }) + + t.Run("SubmitTransaction", func(t *testing.T) { + // TODO: implement + }) + + t.Run("GetFeeComponents", func(t *testing.T) { + ge.On("GetDynamicFee", mock.Anything, mock.Anything).Return(gas.DynamicFee{ + FeeCap: assets.NewWei(big.NewInt(1000000002)), + TipCap: assets.NewWei(big.NewInt(1000000003)), + }, nil).Twice() + + l1Oracle.On("GasPrice", mock.Anything).Return(assets.NewWei(big.NewInt(1000000004)), nil).Once() + ge.On("L1Oracle", mock.Anything).Return(l1Oracle).Once() + var feeComponents *types.ChainFeeComponents + t.Run("Returns valid FeeComponents", func(t *testing.T) { + feeComponents, err = cw.GetFeeComponents(ctx) + require.NoError(t, err) + assert.Equal(t, big.NewInt(1000000002), &feeComponents.ExecutionFee) + assert.Equal(t, big.NewInt(1000000004), &feeComponents.DataAvailabilityFee) + }) + + ge.On("L1Oracle", mock.Anything).Return(nil).Twice() + + t.Run("Returns valid FeeComponents with no L1Oracle", func(t *testing.T) { + feeComponents, err = cw.GetFeeComponents(ctx) + require.NoError(t, err) + assert.Equal(t, big.NewInt(1000000002), &feeComponents.ExecutionFee) + assert.Equal(t, big.NewInt(0), &feeComponents.DataAvailabilityFee) + }) + + t.Run("Returns Legacy Fee for non-EIP1559 enabled gas estimator", func(t *testing.T) { + noDynamicFeeEstimator := gas.NewEvmFeeEstimator(commonLggr.Test(t), getEst, false, geCfg) + var noDyanmicCW ChainWriterService + noDyanmicCW, err = NewChainWriterService(lggr, client, txm, noDynamicFeeEstimator, chainWriterConfig) + ge.On("GetLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(assets.NewWei(big.NewInt(1000000001)), uint64(0), nil).Once() + + feeComponents, err = noDyanmicCW.GetFeeComponents(ctx) + require.NoError(t, err) + assert.Equal(t, big.NewInt(1000000001), &feeComponents.ExecutionFee) + assert.Equal(t, big.NewInt(0), &feeComponents.DataAvailabilityFee) + }) + + t.Run("Fails when neither legacy or dynamic fee is available", func(t *testing.T) { + ge.On("GetDynamicFee", mock.Anything, mock.Anything).Return(gas.DynamicFee{ + FeeCap: nil, + TipCap: nil, + }, nil).Once() + + _, err = cw.GetFeeComponents(ctx) + require.Error(t, err) + }) + + t.Run("Fails when GetFee returns an error", func(t *testing.T) { + expectedErr := fmt.Errorf("GetFee error") + ge.On("GetDynamicFee", mock.Anything, mock.Anything).Return(gas.DynamicFee{ + FeeCap: nil, + TipCap: nil, + }, expectedErr).Once() + _, err = cw.GetFeeComponents(ctx) + require.Equal(t, expectedErr, err) + }) + + t.Run("Fails when L1Oracle returns error", func(t *testing.T) { + ge.On("GetDynamicFee", mock.Anything, mock.Anything).Return(gas.DynamicFee{ + FeeCap: assets.NewWei(big.NewInt(1000000002)), + TipCap: assets.NewWei(big.NewInt(1000000003)), + }, nil).Once() + + ge.On("L1Oracle", mock.Anything).Return(l1Oracle).Once() + + expectedErr := fmt.Errorf("l1Oracle error") + l1Oracle.On("GasPrice", mock.Anything).Return(nil, expectedErr).Once() + _, err = cw.GetFeeComponents(ctx) + require.Equal(t, expectedErr, err) + }) + }) +} + +// Helper functions to remove redundant creation of configs +func newBaseChainWriterConfig() relayevmtypes.ChainWriterConfig { + return relayevmtypes.ChainWriterConfig{ + Contracts: map[string]*relayevmtypes.ContractConfig{ + "forwarder": { + // TODO: Use generic ABI / test contract rather than a keystone specific one + ContractABI: forwarder.KeystoneForwarderABI, + Configs: map[string]*relayevmtypes.ChainWriterDefinition{ + "report": { + ChainSpecificName: "report", + Checker: "simulate", + FromAddress: testutils.NewAddress(), + GasLimit: 200_000, + }, + }, + }, + }, + MaxGasPrice: big.NewInt(1000000000000), + } +} + +func modifyChainWriterConfig(baseConfig relayevmtypes.ChainWriterConfig, modifyFn func(*relayevmtypes.ChainWriterConfig)) relayevmtypes.ChainWriterConfig { + modifiedConfig := baseConfig + modifyFn(&modifiedConfig) + return modifiedConfig +} From 9a54dd05ced1d843fdbcda77295db518e491cfb3 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Wed, 12 Jun 2024 11:09:05 -0400 Subject: [PATCH 15/22] Reverted to testing EvmFeeEstimator rather than EvmEstimator --- .../evm/gas/{helpers.go => helpers_test.go} | 0 core/services/relay/evm/chain_writer_test.go | 82 +++++++------------ 2 files changed, 31 insertions(+), 51 deletions(-) rename core/chains/evm/gas/{helpers.go => helpers_test.go} (100%) diff --git a/core/chains/evm/gas/helpers.go b/core/chains/evm/gas/helpers_test.go similarity index 100% rename from core/chains/evm/gas/helpers.go rename to core/chains/evm/gas/helpers_test.go diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go index 8871fc9ecc6..60a3b41266f 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - commonLggr "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" @@ -23,39 +22,17 @@ import ( relayevmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" ) -type MockGasEstimatorConfig struct { - EIP1559DynamicFeesF bool - BumpPercentF uint16 - BumpThresholdF uint64 - BumpMinF *assets.Wei - LimitMultiplierF float32 - TipCapDefaultF *assets.Wei - TipCapMinF *assets.Wei - PriceMaxF *assets.Wei - PriceMinF *assets.Wei - PriceDefaultF *assets.Wei - FeeCapDefaultF *assets.Wei - LimitMaxF uint64 - ModeF string -} - func TestChainWriter(t *testing.T) { lggr := logger.TestLogger(t) ctx := testutils.Context(t) txm := txmmocks.NewMockEvmTxManager(t) client := evmclimocks.NewClient(t) - ge := gasmocks.NewEvmEstimator(t) - - geCfg := gas.NewMockGasConfig() - - getEst := func(commonLggr.Logger) gas.EvmEstimator { return ge } - - feeEstimator := gas.NewEvmFeeEstimator(commonLggr.Test(t), getEst, true, geCfg) + ge := gasmocks.NewEvmFeeEstimator(t) l1Oracle := rollupmocks.NewL1Oracle(t) chainWriterConfig := newBaseChainWriterConfig() - cw, err := NewChainWriterService(lggr, client, txm, feeEstimator, chainWriterConfig) + cw, err := NewChainWriterService(lggr, client, txm, ge, chainWriterConfig) require.NoError(t, err) @@ -65,7 +42,7 @@ func TestChainWriter(t *testing.T) { invalidAbiConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { cfg.Contracts["forwarder"].ContractABI = "" }) - _, err = NewChainWriterService(lggr, client, txm, feeEstimator, invalidAbiConfig) + _, err = NewChainWriterService(lggr, client, txm, ge, invalidAbiConfig) require.Error(t, err) }) @@ -74,7 +51,7 @@ func TestChainWriter(t *testing.T) { invalidMethodNameConfig := modifyChainWriterConfig(baseConfig, func(cfg *relayevmtypes.ChainWriterConfig) { cfg.Contracts["forwarder"].Configs["report"].ChainSpecificName = "" }) - _, err = NewChainWriterService(lggr, client, txm, feeEstimator, invalidMethodNameConfig) + _, err = NewChainWriterService(lggr, client, txm, ge, invalidMethodNameConfig) require.Error(t, err) }) }) @@ -84,10 +61,11 @@ func TestChainWriter(t *testing.T) { }) t.Run("GetFeeComponents", func(t *testing.T) { - ge.On("GetDynamicFee", mock.Anything, mock.Anything).Return(gas.DynamicFee{ - FeeCap: assets.NewWei(big.NewInt(1000000002)), - TipCap: assets.NewWei(big.NewInt(1000000003)), - }, nil).Twice() + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: assets.NewWei(big.NewInt(1000000001)), + DynamicFeeCap: assets.NewWei(big.NewInt(1000000002)), + DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), + }, uint64(0), nil).Twice() l1Oracle.On("GasPrice", mock.Anything).Return(assets.NewWei(big.NewInt(1000000004)), nil).Once() ge.On("L1Oracle", mock.Anything).Return(l1Oracle).Once() @@ -108,23 +86,24 @@ func TestChainWriter(t *testing.T) { assert.Equal(t, big.NewInt(0), &feeComponents.DataAvailabilityFee) }) - t.Run("Returns Legacy Fee for non-EIP1559 enabled gas estimator", func(t *testing.T) { - noDynamicFeeEstimator := gas.NewEvmFeeEstimator(commonLggr.Test(t), getEst, false, geCfg) - var noDyanmicCW ChainWriterService - noDyanmicCW, err = NewChainWriterService(lggr, client, txm, noDynamicFeeEstimator, chainWriterConfig) - ge.On("GetLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(assets.NewWei(big.NewInt(1000000001)), uint64(0), nil).Once() - - feeComponents, err = noDyanmicCW.GetFeeComponents(ctx) + t.Run("Returns Legacy Fee in absence of Dynamic Fee", func(t *testing.T) { + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: assets.NewWei(big.NewInt(1000000001)), + DynamicFeeCap: nil, + DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), + }, uint64(0), nil).Once() + feeComponents, err = cw.GetFeeComponents(ctx) require.NoError(t, err) assert.Equal(t, big.NewInt(1000000001), &feeComponents.ExecutionFee) assert.Equal(t, big.NewInt(0), &feeComponents.DataAvailabilityFee) }) t.Run("Fails when neither legacy or dynamic fee is available", func(t *testing.T) { - ge.On("GetDynamicFee", mock.Anything, mock.Anything).Return(gas.DynamicFee{ - FeeCap: nil, - TipCap: nil, - }, nil).Once() + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: nil, + DynamicFeeCap: nil, + DynamicTipCap: nil, + }, uint64(0), nil).Once() _, err = cw.GetFeeComponents(ctx) require.Error(t, err) @@ -132,20 +111,21 @@ func TestChainWriter(t *testing.T) { t.Run("Fails when GetFee returns an error", func(t *testing.T) { expectedErr := fmt.Errorf("GetFee error") - ge.On("GetDynamicFee", mock.Anything, mock.Anything).Return(gas.DynamicFee{ - FeeCap: nil, - TipCap: nil, - }, expectedErr).Once() + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: nil, + DynamicFeeCap: nil, + DynamicTipCap: nil, + }, uint64(0), expectedErr).Once() _, err = cw.GetFeeComponents(ctx) require.Equal(t, expectedErr, err) }) t.Run("Fails when L1Oracle returns error", func(t *testing.T) { - ge.On("GetDynamicFee", mock.Anything, mock.Anything).Return(gas.DynamicFee{ - FeeCap: assets.NewWei(big.NewInt(1000000002)), - TipCap: assets.NewWei(big.NewInt(1000000003)), - }, nil).Once() - + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + Legacy: assets.NewWei(big.NewInt(1000000001)), + DynamicFeeCap: assets.NewWei(big.NewInt(1000000002)), + DynamicTipCap: assets.NewWei(big.NewInt(1000000003)), + }, uint64(0), nil).Once() ge.On("L1Oracle", mock.Anything).Return(l1Oracle).Once() expectedErr := fmt.Errorf("l1Oracle error") From 4fb2a0f39e7a9f5e81cbae39aaaafbf51ef0535c Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Thu, 13 Jun 2024 11:16:37 -0400 Subject: [PATCH 16/22] reordered imports on CW --- core/services/relay/evm/chain_writer.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 91bed6c5083..7c375dc75eb 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -6,23 +6,22 @@ import ( "math/big" "strings" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" commonservices "github.com/smartcontractkit/chainlink-common/pkg/services" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" ) type ChainWriterService interface { From 33e13d158a12322cc0e04fe81990204d7174f534 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Thu, 13 Jun 2024 13:18:21 -0400 Subject: [PATCH 17/22] Added maxGasPrice default --- core/services/relay/evm/chain_writer.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 7c375dc75eb..f6f05968fa0 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -33,12 +33,17 @@ type ChainWriterService interface { var _ ChainWriterService = (*chainWriter)(nil) func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, estimator gas.EvmFeeEstimator, config types.ChainWriterConfig) (ChainWriterService, error) { + maxGasPrice := config.MaxGasPrice + if maxGasPrice == nil { + maxGasPrice = assets.GWei(500).ToInt() + } + w := chainWriter{ logger: logger, client: client, txm: txm, ge: estimator, - maxGasPrice: config.MaxGasPrice, + maxGasPrice: maxGasPrice, sendStrategy: txmgr.NewSendEveryStrategy(), contracts: config.Contracts, From eb2f663b22be90b049e1e05971bccdb42a312cda Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Thu, 13 Jun 2024 14:01:23 -0400 Subject: [PATCH 18/22] review requests --- core/services/relay/evm/chain_writer.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index f6f05968fa0..1b4a89dfe24 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -33,9 +33,8 @@ type ChainWriterService interface { var _ ChainWriterService = (*chainWriter)(nil) func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm evmtxmgr.TxManager, estimator gas.EvmFeeEstimator, config types.ChainWriterConfig) (ChainWriterService, error) { - maxGasPrice := config.MaxGasPrice - if maxGasPrice == nil { - maxGasPrice = assets.GWei(500).ToInt() + if config.MaxGasPrice == nil { + return nil, fmt.Errorf("max gas price is required") } w := chainWriter{ @@ -43,7 +42,7 @@ func NewChainWriterService(logger logger.Logger, client evmclient.Client, txm ev client: client, txm: txm, ge: estimator, - maxGasPrice: maxGasPrice, + maxGasPrice: config.MaxGasPrice, sendStrategy: txmgr.NewSendEveryStrategy(), contracts: config.Contracts, @@ -171,7 +170,7 @@ func (w *chainWriter) GetTransactionStatus(ctx context.Context, transactionID uu // (if the chain doesn't support dynamic TXs) the legacy GasPrice is used. func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainFeeComponents, error) { if w.ge == nil { - return nil, fmt.Errorf("not implemented") + return nil, fmt.Errorf("gas estimator not available") } fee, _, err := w.ge.GetFee(ctx, nil, 0, assets.NewWei(w.maxGasPrice)) @@ -184,7 +183,7 @@ func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainF gasPrice = fee.DynamicFeeCap.ToInt() } if gasPrice == nil { - return nil, fmt.Errorf("Dynamic fee and legacy gas price missing %+v", fee) + return nil, fmt.Errorf("dynamic fee and legacy gas price missing %+v", fee) } l1Oracle := w.ge.L1Oracle() if l1Oracle == nil { From be779f4cceb3ecd061a6ab66f8386d719abce057 Mon Sep 17 00:00:00 2001 From: Silas Lenihan <32529249+silaslenihan@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:02:46 -0400 Subject: [PATCH 19/22] Update core/services/relay/evm/write_target.go Co-authored-by: Jordan Krage --- core/services/relay/evm/write_target.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/services/relay/evm/write_target.go b/core/services/relay/evm/write_target.go index 2edc67136ec..46f4f83b05b 100644 --- a/core/services/relay/evm/write_target.go +++ b/core/services/relay/evm/write_target.go @@ -70,7 +70,8 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain }, } - cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), nil, chainWriterConfig) + chainWriterConfig.MaxGasPrice = chain.Config().EVM().GasEstimator().PriceMax() + cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), chain.GasEstimator(), chainWriterConfig) if err != nil { return nil, err } From 52cf4e61fd6454ebc403bad33055e5c9a5e79d6a Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Thu, 13 Jun 2024 14:23:37 -0400 Subject: [PATCH 20/22] Fixed maxGasPrice type --- core/services/relay/evm/write_target.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/services/relay/evm/write_target.go b/core/services/relay/evm/write_target.go index 46f4f83b05b..76353b6d76a 100644 --- a/core/services/relay/evm/write_target.go +++ b/core/services/relay/evm/write_target.go @@ -70,7 +70,7 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain }, } - chainWriterConfig.MaxGasPrice = chain.Config().EVM().GasEstimator().PriceMax() + chainWriterConfig.MaxGasPrice = chain.Config().EVM().GasEstimator().PriceMax().ToInt() cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), chain.GasEstimator(), chainWriterConfig) if err != nil { return nil, err From fadea16b524d85771c268904d1b1064fb744f226 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Thu, 13 Jun 2024 14:28:35 -0400 Subject: [PATCH 21/22] changed maxGasPrice to assets.Wei --- core/services/relay/evm/chain_writer.go | 4 ++-- core/services/relay/evm/chain_writer_test.go | 2 +- core/services/relay/evm/types/types.go | 4 ++-- core/services/relay/evm/write_target.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 1b4a89dfe24..52bea212b51 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -72,7 +72,7 @@ type chainWriter struct { client evmclient.Client txm evmtxmgr.TxManager ge gas.EvmFeeEstimator - maxGasPrice *big.Int + maxGasPrice *assets.Wei sendStrategy txmgrtypes.TxStrategy contracts map[string]*types.ContractConfig @@ -173,7 +173,7 @@ func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainF return nil, fmt.Errorf("gas estimator not available") } - fee, _, err := w.ge.GetFee(ctx, nil, 0, assets.NewWei(w.maxGasPrice)) + fee, _, err := w.ge.GetFee(ctx, nil, 0, w.maxGasPrice) if err != nil { return nil, err } diff --git a/core/services/relay/evm/chain_writer_test.go b/core/services/relay/evm/chain_writer_test.go index 60a3b41266f..b5aa82ffd82 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -153,7 +153,7 @@ func newBaseChainWriterConfig() relayevmtypes.ChainWriterConfig { }, }, }, - MaxGasPrice: big.NewInt(1000000000000), + MaxGasPrice: assets.NewWeiI(1000000000000), } } diff --git a/core/services/relay/evm/types/types.go b/core/services/relay/evm/types/types.go index fe02878c5e9..fe62b5edf6e 100644 --- a/core/services/relay/evm/types/types.go +++ b/core/services/relay/evm/types/types.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - mathBig "math/big" "github.com/ethereum/go-ethereum/common" "github.com/lib/pq" @@ -20,13 +19,14 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" ) type ChainWriterConfig struct { Contracts map[string]*ContractConfig SendStrategy txmgrtypes.TxStrategy - MaxGasPrice *mathBig.Int + MaxGasPrice *assets.Wei } type ContractConfig struct { diff --git a/core/services/relay/evm/write_target.go b/core/services/relay/evm/write_target.go index 76353b6d76a..46f4f83b05b 100644 --- a/core/services/relay/evm/write_target.go +++ b/core/services/relay/evm/write_target.go @@ -70,7 +70,7 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain }, } - chainWriterConfig.MaxGasPrice = chain.Config().EVM().GasEstimator().PriceMax().ToInt() + chainWriterConfig.MaxGasPrice = chain.Config().EVM().GasEstimator().PriceMax() cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), chain.GasEstimator(), chainWriterConfig) if err != nil { return nil, err From d98331f2326dca24c16b3d9836be02ead4bb3341 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Fri, 14 Jun 2024 09:33:55 -0400 Subject: [PATCH 22/22] mocked gasEstimator --- core/services/relay/evm/write_target_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/services/relay/evm/write_target_test.go b/core/services/relay/evm/write_target_test.go index 4abbf16cd3b..95d0617db4f 100644 --- a/core/services/relay/evm/write_target_test.go +++ b/core/services/relay/evm/write_target_test.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" evmcapabilities "github.com/smartcontractkit/chainlink/v2/core/capabilities" evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" + gasmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" txmmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -61,8 +62,10 @@ func TestEvmWrite(t *testing.T) { c.EVM[0].Workflow.ForwarderAddress = &forwarderAddr }) evmCfg := evmtest.NewChainScopedConfig(t, cfg) + ge := gasmocks.NewEvmFeeEstimator(t) chain.On("Config").Return(evmCfg) + chain.On("GasEstimator").Return(ge) db := pgtest.NewSqlxDB(t) keyStore := cltest.NewKeyStore(t, db)