Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ChainWriter EVM GetFeeComponents implementation #13413

Merged
merged 46 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
171b798
ChainWriter EVM GetFeeComponents implementation
silaslenihan Jun 4, 2024
399acd5
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 4, 2024
7ea570c
Fixed Write Target CW initialization
silaslenihan Jun 4, 2024
cc2542b
added changeset
silaslenihan Jun 4, 2024
857c7da
Added ChainWriter tests
silaslenihan Jun 5, 2024
0a9200e
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 5, 2024
86b5f80
linting fix
silaslenihan Jun 5, 2024
df2be0c
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 5, 2024
cef576d
Updated comment
silaslenihan Jun 6, 2024
3d93e35
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 6, 2024
44b6347
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 6, 2024
2dc28c7
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 6, 2024
f0d2ca5
Moved MaxGasPrice to CW Config
silaslenihan Jun 10, 2024
1c75124
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 10, 2024
094c97f
Changed ExecGasEstimator to CWGasEstimator
silaslenihan Jun 10, 2024
db23c13
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 10, 2024
d0e2d72
removed tests from this PR
silaslenihan Jun 10, 2024
ce9968e
Added comments and updated cw struct
silaslenihan Jun 11, 2024
6a7ea30
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 11, 2024
1e8b94b
renamed gasPriceWei to evmFee
silaslenihan Jun 11, 2024
684185a
Added more specific error for gasPrice == nil
silaslenihan Jun 11, 2024
efa22e7
Update core/services/relay/evm/chain_writer.go
silaslenihan Jun 11, 2024
de0bfda
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 11, 2024
b94d557
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 12, 2024
02f0e0a
Added tests back in
silaslenihan Jun 12, 2024
9a54dd0
Reverted to testing EvmFeeEstimator rather than EvmEstimator
silaslenihan Jun 12, 2024
814db11
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 12, 2024
bd0072d
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 12, 2024
2df23aa
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 12, 2024
7ac7c0d
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 13, 2024
4fb2a0f
reordered imports on CW
silaslenihan Jun 13, 2024
d647dee
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 13, 2024
33e13d1
Added maxGasPrice default
silaslenihan Jun 13, 2024
c8f4714
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 13, 2024
eb2f663
review requests
silaslenihan Jun 13, 2024
be779f4
Update core/services/relay/evm/write_target.go
silaslenihan Jun 13, 2024
445b50f
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 13, 2024
52cf4e6
Fixed maxGasPrice type
silaslenihan Jun 13, 2024
fadea16
changed maxGasPrice to assets.Wei
silaslenihan Jun 13, 2024
296f961
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 13, 2024
d98331f
mocked gasEstimator
silaslenihan Jun 14, 2024
b1d4d1b
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 14, 2024
95f0328
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 14, 2024
1e23df6
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 14, 2024
96548b2
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 14, 2024
a69db6e
Merge branch 'develop' into feat/cw-get-fee-components-evm
silaslenihan Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fuzzy-frogs-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

#added EVM implementation of GetFeeComponents function for ChainWriter
64 changes: 54 additions & 10 deletions core/services/relay/evm/chain_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ import (
"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 {
Expand All @@ -30,11 +32,17 @@ 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, config types.ChainWriterConfig) (ChainWriterService, error) {
if config.MaxGasPrice == nil {
return nil, fmt.Errorf("max gas price is required")
}

w := chainWriter{
logger: logger,
client: client,
txm: txm,
logger: logger,
client: client,
txm: txm,
ge: estimator,
maxGasPrice: config.MaxGasPrice,
silaslenihan marked this conversation as resolved.
Show resolved Hide resolved

sendStrategy: txmgr.NewSendEveryStrategy(),
contracts: config.Contracts,
Expand All @@ -60,9 +68,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
logger logger.Logger
client evmclient.Client
txm evmtxmgr.TxManager
ge gas.EvmFeeEstimator
maxGasPrice *assets.Wei

sendStrategy txmgrtypes.TxStrategy
contracts map[string]*types.ContractConfig
Expand Down Expand Up @@ -155,8 +165,42 @@ func (w *chainWriter) GetTransactionStatus(ctx context.Context, transactionID uu
return commontypes.Unknown, fmt.Errorf("not implemented")
}

// 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) {
return nil, fmt.Errorf("not implemented")
if w.ge == nil {
return nil, fmt.Errorf("gas estimator not available")
}

fee, _, err := w.ge.GetFee(ctx, nil, 0, w.maxGasPrice)
if err != nil {
return nil, err
}
// Use legacy if no dynamic is available.
gasPrice := fee.Legacy.ToInt()
if fee.DynamicFeeCap != nil {
gasPrice = fee.DynamicFeeCap.ToInt()
}
if gasPrice == nil {
return nil, fmt.Errorf("dynamic fee and legacy gas price missing %+v", fee)
}
l1Oracle := w.ge.L1Oracle()
if l1Oracle == nil {
return &commontypes.ChainFeeComponents{
ExecutionFee: *gasPrice,
DataAvailabilityFee: *big.NewInt(0),
}, nil
}
l1OracleFee, err := l1Oracle.GasPrice(ctx)
silaslenihan marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

return &commontypes.ChainFeeComponents{
ExecutionFee: *gasPrice,
DataAvailabilityFee: *big.NewInt(l1OracleFee.Int64()),
}, nil
}

func (w *chainWriter) Close() error {
Expand Down
164 changes: 164 additions & 0 deletions core/services/relay/evm/chain_writer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
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("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: assets.NewWeiI(1000000000000),
}
}

func modifyChainWriterConfig(baseConfig relayevmtypes.ChainWriterConfig, modifyFn func(*relayevmtypes.ChainWriterConfig)) relayevmtypes.ChainWriterConfig {
modifiedConfig := baseConfig
modifyFn(&modifiedConfig)
return modifiedConfig
}
2 changes: 2 additions & 0 deletions core/services/relay/evm/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/codec"
"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/logpoller"
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big"
Expand All @@ -27,6 +28,7 @@ import (
type ChainWriterConfig struct {
Contracts map[string]*ContractConfig
SendStrategy txmgrtypes.TxStrategy
MaxGasPrice *assets.Wei
}

type ContractConfig struct {
Expand Down
4 changes: 3 additions & 1 deletion core/services/relay/evm/write_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ func NewWriteTarget(ctx context.Context, relayer *Relayer, chain legacyevm.Chain
},
},
}
cw, err := NewChainWriterService(lggr.Named("ChainWriter"), chain.Client(), chain.TxManager(), 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
}
Expand Down
3 changes: 3 additions & 0 deletions core/services/relay/evm/write_target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
Loading