From 7c1c746b8f3dcda9a257fbc33806f9570f85b6a1 Mon Sep 17 00:00:00 2001 From: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> Date: Mon, 26 Aug 2024 19:36:40 -0700 Subject: [PATCH 01/31] cherry-pick - feat: Add support for parsing deployed 1.0 contracts which have no TypeAndVersion(#1368) (#1369) ## Motivation - 1.0.0 contracts have no TypeAndVersion however tooling validation expects a TypeAndVersion be set ## Solution - Return `Unknown 1.0.0` for contracts with an empty type and version string, implying that they are 1.0.0 deployed contracts ## Motivation ## Solution Co-authored-by: Peter Olds --- .../plugins/ccip/config/type_and_version.go | 7 +++ .../ccip/config/type_and_version_test.go | 49 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 core/services/ocr2/plugins/ccip/config/type_and_version_test.go diff --git a/core/services/ocr2/plugins/ccip/config/type_and_version.go b/core/services/ocr2/plugins/ccip/config/type_and_version.go index fdfd892b08..00e3b26d4e 100644 --- a/core/services/ocr2/plugins/ccip/config/type_and_version.go +++ b/core/services/ocr2/plugins/ccip/config/type_and_version.go @@ -19,6 +19,7 @@ var ( EVM2EVMOffRamp ContractType = "EVM2EVMOffRamp" CommitStore ContractType = "CommitStore" PriceRegistry ContractType = "PriceRegistry" + Unknown ContractType = "Unknown" // 1.0.0 Contracts which have no TypeAndVersion ContractTypes = mapset.NewSet[ContractType]( EVM2EVMOffRamp, EVM2EVMOnRamp, @@ -63,7 +64,13 @@ func TypeAndVersion(addr common.Address, client bind.ContractBackend) (ContractT return ContractType(contractType), *v, nil } +// default version to use when TypeAndVersion is missing. +const defaultVersion = "1.0.0" + func ParseTypeAndVersion(tvStr string) (string, string, error) { + if tvStr == "" { + tvStr = string(Unknown) + " " + defaultVersion + } typeAndVersionValues := strings.Split(tvStr, " ") if len(typeAndVersionValues) < 2 { diff --git a/core/services/ocr2/plugins/ccip/config/type_and_version_test.go b/core/services/ocr2/plugins/ccip/config/type_and_version_test.go new file mode 100644 index 0000000000..f626389dcd --- /dev/null +++ b/core/services/ocr2/plugins/ccip/config/type_and_version_test.go @@ -0,0 +1,49 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseTypeAndVersion(t *testing.T) { + tests := []struct { + name string + input string + expectedType string + expectedVersion string + expectedError string + }{ + { + name: "Valid input", + input: string(EVM2EVMOnRamp) + " 1.2.0", + expectedType: string(EVM2EVMOnRamp), + expectedVersion: "1.2.0", + }, + { + name: "Empty input", + input: "", + expectedType: string(Unknown), + expectedVersion: defaultVersion, + }, + { + name: "Invalid input", + input: "InvalidInput", + expectedError: "invalid type and version InvalidInput", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actualType, actualVersion, err := ParseTypeAndVersion(tc.input) + + if tc.expectedError != "" { + assert.EqualError(t, err, tc.expectedError) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedType, actualType) + assert.Equal(t, tc.expectedVersion, actualVersion) + } + }) + } +} From 5c711214167b7e6f05cf6de74bdab9f6e26763b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedemann=20F=C3=BCrst?= <59653747+friedemannf@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:39:21 +0200 Subject: [PATCH 02/31] Bump chain-selectors => v1.0.23 (#1395) Bumping chain-selectors to https://github.com/smartcontractkit/chain-selectors/pull/53 --- core/scripts/ccip/manual-execution/go.mod | 2 +- core/scripts/ccip/manual-execution/go.sum | 4 ++-- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/scripts/ccip/manual-execution/go.mod b/core/scripts/ccip/manual-execution/go.mod index 8d646c03f8..ec295ec868 100644 --- a/core/scripts/ccip/manual-execution/go.mod +++ b/core/scripts/ccip/manual-execution/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/ethereum/go-ethereum v1.11.3 github.com/pkg/errors v0.9.1 - github.com/smartcontractkit/chain-selectors v1.0.21 + github.com/smartcontractkit/chain-selectors v1.0.23 go.uber.org/multierr v1.1.0 golang.org/x/crypto v0.1.0 ) diff --git a/core/scripts/ccip/manual-execution/go.sum b/core/scripts/ccip/manual-execution/go.sum index d79872f13f..a5bf3ee297 100644 --- a/core/scripts/ccip/manual-execution/go.sum +++ b/core/scripts/ccip/manual-execution/go.sum @@ -70,8 +70,8 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E= -github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= +github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 13ed1c4aa9..0fdab93d49 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -22,7 +22,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/shopspring/decimal v1.4.0 - github.com/smartcontractkit/chain-selectors v1.0.21 + github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 468c13a670..42385b4041 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1066,8 +1066,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E= -github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= +github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/go.mod b/go.mod index 683793cb53..92fb1c4306 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/scylladb/go-reflectx v1.0.1 github.com/shirou/gopsutil/v3 v3.24.3 github.com/shopspring/decimal v1.4.0 - github.com/smartcontractkit/chain-selectors v1.0.21 + github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 diff --git a/go.sum b/go.sum index 5ad6772ecd..324c4f9ec7 100644 --- a/go.sum +++ b/go.sum @@ -1023,8 +1023,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E= -github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= +github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 7abf19dc82..b2632668f1 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -34,7 +34,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685 - github.com/smartcontractkit/chain-selectors v1.0.21 + github.com/smartcontractkit/chain-selectors v1.0.23 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index babc3fc8c6..ceb9cacd73 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1390,8 +1390,8 @@ github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685 h1:jakAsdhDxV4cMgRAcSvHraXjyePi8umG5SEUTGFvuy8= github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685/go.mod h1:p7L/xNEQpHDdZtgFA6/FavuZHqvV3kYhQysxBywmq1k= -github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E= -github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= +github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index a90826f599..b89026ac9b 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -374,7 +374,7 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smartcontractkit/chain-selectors v1.0.21 // indirect + github.com/smartcontractkit/chain-selectors v1.0.23 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 536b96f8b6..1ab5063462 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1354,8 +1354,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ= github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= -github.com/smartcontractkit/chain-selectors v1.0.21 h1:KCR9SA7PhOexaBzFieHoLv1WonwhVOPtOStpqTmLC4E= -github.com/smartcontractkit/chain-selectors v1.0.21/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= +github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= From a44b9c238e5a186b700588c501822c97fe22e0a8 Mon Sep 17 00:00:00 2001 From: nogo <110664798+0xnogo@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:18:02 +0400 Subject: [PATCH 03/31] Fix RPCClient Deadlock on Unsubscribe and NewHead (#14236) (#1426) Cherry-pick of https://github.com/smartcontractkit/chainlink/pull/14236 --------- Co-authored-by: Dmytro Haidashenko <34754799+dhaidashenko@users.noreply.github.com> --- .changeset/curly-birds-guess.md | 5 +++++ core/chains/evm/client/rpc_client.go | 15 ++++++++----- core/chains/evm/client/rpc_client_test.go | 27 +++++++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 .changeset/curly-birds-guess.md diff --git a/.changeset/curly-birds-guess.md b/.changeset/curly-birds-guess.md new file mode 100644 index 0000000000..c66bd54178 --- /dev/null +++ b/.changeset/curly-birds-guess.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Fixed deadlock in RPCClient causing CL Node to stop performing RPC requests for the affected chain #bugfix diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index 07aa86fc45..72071199d4 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -141,6 +141,7 @@ type rpcClient struct { // stateMu since it can happen on state transitions as well as rpcClient Close. chStopInFlight chan struct{} + chainInfoLock sync.RWMutex // intercepted values seen by callers of the rpcClient excluding health check calls. Need to ensure MultiNode provides repeatable read guarantee highestUserObservations commonclient.ChainInfo // most recent chain info observed during current lifecycle (reseted on DisconnectAll) @@ -336,7 +337,9 @@ func (r *rpcClient) DisconnectAll() { } r.cancelInflightRequests() r.unsubscribeAll() + r.chainInfoLock.Lock() r.latestChainInfo = commonclient.ChainInfo{} + r.chainInfoLock.Unlock() } // unsubscribeAll unsubscribes all subscriptions @@ -1379,8 +1382,8 @@ func (r *rpcClient) onNewHead(ctx context.Context, requestCh <-chan struct{}, he return } - r.stateMu.Lock() - defer r.stateMu.Unlock() + r.chainInfoLock.Lock() + defer r.chainInfoLock.Unlock() if !commonclient.CtxIsHeathCheckRequest(ctx) { r.highestUserObservations.BlockNumber = max(r.highestUserObservations.BlockNumber, head.Number) r.highestUserObservations.TotalDifficulty = commonclient.MaxTotalDifficulty(r.highestUserObservations.TotalDifficulty, head.TotalDifficulty) @@ -1398,8 +1401,8 @@ func (r *rpcClient) onNewFinalizedHead(ctx context.Context, requestCh <-chan str if head == nil { return } - r.stateMu.Lock() - defer r.stateMu.Unlock() + r.chainInfoLock.Lock() + defer r.chainInfoLock.Unlock() if !commonclient.CtxIsHeathCheckRequest(ctx) { r.highestUserObservations.FinalizedBlockNumber = max(r.highestUserObservations.FinalizedBlockNumber, head.Number) } @@ -1412,8 +1415,8 @@ func (r *rpcClient) onNewFinalizedHead(ctx context.Context, requestCh <-chan str } func (r *rpcClient) GetInterceptedChainInfo() (latest, highestUserObservations commonclient.ChainInfo) { - r.stateMu.RLock() - defer r.stateMu.RUnlock() + r.chainInfoLock.RLock() + defer r.chainInfoLock.RUnlock() return r.latestChainInfo, r.highestUserObservations } diff --git a/core/chains/evm/client/rpc_client_test.go b/core/chains/evm/client/rpc_client_test.go index 1282188099..07e097727a 100644 --- a/core/chains/evm/client/rpc_client_test.go +++ b/core/chains/evm/client/rpc_client_test.go @@ -7,6 +7,7 @@ import ( "fmt" "math/big" "net/url" + "sync" "testing" "time" @@ -130,6 +131,32 @@ func TestRPCClient_SubscribeNewHead(t *testing.T) { assert.Equal(t, int64(0), highestUserObservations.FinalizedBlockNumber) assert.Equal(t, (*big.Int)(nil), highestUserObservations.TotalDifficulty) }) + t.Run("Concurrent Unsubscribe and onNewHead calls do not lead to a deadlock", func(t *testing.T) { + const numberOfAttempts = 1000 // need a large number to increase the odds of reproducing the issue + server := testutils.NewWSServer(t, chainId, serverCallBack) + wsURL := server.WSURL() + + rpc := client.NewRPCClient(lggr, *wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + var wg sync.WaitGroup + for i := 0; i < numberOfAttempts; i++ { + ch := make(chan *evmtypes.Head) + sub, err := rpc.SubscribeNewHead(tests.Context(t), ch) + require.NoError(t, err) + wg.Add(2) + go func() { + server.MustWriteBinaryMessageSync(t, makeNewHeadWSMessage(&evmtypes.Head{Number: 256, TotalDifficulty: big.NewInt(1000)})) + wg.Done() + }() + go func() { + rpc.UnsubscribeAllExceptAliveLoop() + sub.Unsubscribe() + wg.Done() + }() + wg.Wait() + } + }) t.Run("Block's chain ID matched configured", func(t *testing.T) { server := testutils.NewWSServer(t, chainId, serverCallBack) wsURL := server.WSURL() From 22d3d18b228a3d147a6d9d98ff5ce94913e25e96 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Thu, 12 Sep 2024 11:54:28 +0400 Subject: [PATCH 04/31] Bump version for CCIP 2.14.0-ccip1.5.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 541cfb1aaf..e9931ed7cd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ccip", - "version": "2.14.0-ccip1.5.0", + "version": "2.14.0-ccip1.5.2", "description": "node of the decentralized oracle network, bridging on and off-chain computation", "main": "index.js", "scripts": { From 16dda4cf834cbe2af95dab19088bfcff35ab27b0 Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:15:45 -0500 Subject: [PATCH 05/31] Gas limit estimation feature (#14041) * Added gas limit estimation feature to EVM gas estimators * Added changeset * Fixed linting * Added new failure tests * Fixed test * Fixed mock configs in ccip capabilities * Fixed configs * Fixed test * Refactored GetFee method * Added EstimatedGasBuffer and addressed feedback * Fixed linting * Fixed config doc and tests * Addressed feedback * Fixed typo * Repurposed LimitMultiplier to be used as a buffer for estimated gas * Updated mocks * Removed LimitMultiplier if gas estimation fails and updated config docs * Fixed Broadcaster test * Enabled uncapped gas limit estimation if provided fee limit is 0 * Updated estimate gas buffer to be a fixed value instead of using LimitMultiplier * Updated log message * Updated logs --------- Co-authored-by: Prashant Yadav <34992934+prashantkumar1982@users.noreply.github.com> --- .changeset/loud-windows-call.md | 5 + common/fee/models.go | 1 + common/txmgr/broadcaster.go | 7 +- .../ocrimpls/contract_transmitter_test.go | 1 + .../evm/config/chain_scoped_gas_estimator.go | 4 + core/chains/evm/config/config.go | 1 + core/chains/evm/config/mocks/gas_estimator.go | 45 ++++ core/chains/evm/config/toml/config.go | 14 +- .../evm/config/toml/defaults/fallback.toml | 1 + core/chains/evm/gas/helpers_test.go | 5 + .../chains/evm/gas/mocks/evm_fee_estimator.go | 76 +++---- .../evm/gas/mocks/fee_estimator_client.go | 57 +++++ core/chains/evm/gas/models.go | 89 ++++++-- core/chains/evm/gas/models_test.go | 199 ++++++++++++++++-- core/chains/evm/txmgr/attempts.go | 6 +- core/chains/evm/txmgr/attempts_test.go | 2 +- core/chains/evm/txmgr/broadcaster_test.go | 81 ++++++- core/chains/evm/txmgr/confirmer_test.go | 10 +- core/chains/evm/txmgr/stuck_tx_detector.go | 4 +- .../evm/txmgr/stuck_tx_detector_test.go | 4 +- core/chains/evm/txmgr/test_helpers.go | 1 + core/config/docs/chains-evm.toml | 2 + core/internal/features/features_test.go | 4 +- core/services/chainlink/config_test.go | 2 + .../chainlink/testdata/config-full.toml | 1 + .../config-multi-chain-effective.toml | 3 + core/services/keeper/upkeep_executer_test.go | 2 +- .../ccipdata/commit_store_reader_test.go | 2 +- .../ccip/prices/exec_price_estimator.go | 2 +- .../ccip/prices/exec_price_estimator_test.go | 3 +- .../evmregistry/v21/gasprice/gasprice.go | 2 +- .../evmregistry/v21/gasprice/gasprice_test.go | 6 +- core/services/relay/evm/chain_writer.go | 2 +- core/services/relay/evm/chain_writer_test.go | 10 +- core/web/evm_transfer_controller.go | 6 +- core/web/resolver/testdata/config-full.toml | 1 + .../config-multi-chain-effective.toml | 3 + docs/CONFIG.md | 68 ++++++ .../disk-based-logging-disabled.txtar | 1 + .../validate/disk-based-logging-no-dir.txtar | 1 + .../node/validate/disk-based-logging.txtar | 1 + testdata/scripts/node/validate/invalid.txtar | 1 + testdata/scripts/node/validate/valid.txtar | 1 + 43 files changed, 621 insertions(+), 116 deletions(-) create mode 100644 .changeset/loud-windows-call.md diff --git a/.changeset/loud-windows-call.md b/.changeset/loud-windows-call.md new file mode 100644 index 0000000000..e05bed706a --- /dev/null +++ b/.changeset/loud-windows-call.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Added gas limit estimation feature to EVM gas estimators #added diff --git a/common/fee/models.go b/common/fee/models.go index 0568a2f143..0496d3929c 100644 --- a/common/fee/models.go +++ b/common/fee/models.go @@ -14,6 +14,7 @@ var ( ErrBumpFeeExceedsLimit = errors.New("fee bump exceeds limit") ErrBump = errors.New("fee bump failed") ErrConnectivity = errors.New("transaction propagation issue: transactions are not being mined") + ErrFeeLimitTooLow = errors.New("provided fee limit too low") ) func IsBumpErr(err error) bool { diff --git a/common/txmgr/broadcaster.go b/common/txmgr/broadcaster.go index be3d3ca2f6..1606f58ce0 100644 --- a/common/txmgr/broadcaster.go +++ b/common/txmgr/broadcaster.go @@ -20,6 +20,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink/v2/common/client" + commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" @@ -434,7 +435,11 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand } attempt, _, _, retryable, err := eb.NewTxAttempt(ctx, *etx, eb.lggr) - if err != nil { + // Mark transaction as fatal if provided gas limit is set too low + if errors.Is(err, commonfee.ErrFeeLimitTooLow) { + etx.Error = null.StringFrom(commonfee.ErrFeeLimitTooLow.Error()) + return eb.saveFatallyErroredTransaction(eb.lggr, etx), false + } else if err != nil { return fmt.Errorf("processUnstartedTxs failed on NewAttempt: %w", err), retryable } diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 4e0a7162aa..bd0bf9a2e3 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -612,6 +612,7 @@ func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType { func (g *TestGasEstimatorConfig) PriceMaxKey(addr common.Address) *assets.Wei { return assets.GWei(1) } +func (g *TestGasEstimatorConfig) EstimateGasLimit() bool { return false } func (e *TestEvmConfig) GasEstimator() evmconfig.GasEstimator { return &TestGasEstimatorConfig{bumpThreshold: e.BumpThreshold} diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index 689d5e38b8..4f2d8872d8 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -108,6 +108,10 @@ func (g *gasEstimatorConfig) LimitJobType() LimitJobType { return &limitJobTypeConfig{c: g.c.LimitJobType} } +func (g *gasEstimatorConfig) EstimateGasLimit() bool { + return *g.c.EstimateGasLimit +} + type limitJobTypeConfig struct { c toml.GasLimitJobType } diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index 3ccdfeea8b..9517c68716 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -136,6 +136,7 @@ type GasEstimator interface { PriceMin() *assets.Wei Mode() string PriceMaxKey(gethcommon.Address) *assets.Wei + EstimateGasLimit() bool } type LimitJobType interface { diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go index b8e813e806..40c10757b8 100644 --- a/core/chains/evm/config/mocks/gas_estimator.go +++ b/core/chains/evm/config/mocks/gas_estimator.go @@ -298,6 +298,51 @@ func (_c *GasEstimator_EIP1559DynamicFees_Call) RunAndReturn(run func() bool) *G return _c } +// EstimateGasLimit provides a mock function with given fields: +func (_m *GasEstimator) EstimateGasLimit() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for EstimateGasLimit") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// GasEstimator_EstimateGasLimit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateGasLimit' +type GasEstimator_EstimateGasLimit_Call struct { + *mock.Call +} + +// EstimateGasLimit is a helper method to define mock.On call +func (_e *GasEstimator_Expecter) EstimateGasLimit() *GasEstimator_EstimateGasLimit_Call { + return &GasEstimator_EstimateGasLimit_Call{Call: _e.mock.On("EstimateGasLimit")} +} + +func (_c *GasEstimator_EstimateGasLimit_Call) Run(run func()) *GasEstimator_EstimateGasLimit_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GasEstimator_EstimateGasLimit_Call) Return(_a0 bool) *GasEstimator_EstimateGasLimit_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GasEstimator_EstimateGasLimit_Call) RunAndReturn(run func() bool) *GasEstimator_EstimateGasLimit_Call { + _c.Call.Return(run) + return _c +} + // FeeCapDefault provides a mock function with given fields: func (_m *GasEstimator) FeeCapDefault() *assets.Wei { ret := _m.Called() diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index ac7841ac49..99e61a394b 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -549,11 +549,12 @@ type GasEstimator struct { PriceMax *assets.Wei PriceMin *assets.Wei - LimitDefault *uint64 - LimitMax *uint64 - LimitMultiplier *decimal.Decimal - LimitTransfer *uint64 - LimitJobType GasLimitJobType `toml:",omitempty"` + LimitDefault *uint64 + LimitMax *uint64 + LimitMultiplier *decimal.Decimal + LimitTransfer *uint64 + LimitJobType GasLimitJobType `toml:",omitempty"` + EstimateGasLimit *bool BumpMin *assets.Wei BumpPercent *uint16 @@ -641,6 +642,9 @@ func (e *GasEstimator) setFrom(f *GasEstimator) { if v := f.LimitTransfer; v != nil { e.LimitTransfer = v } + if v := f.EstimateGasLimit; v != nil { + e.EstimateGasLimit = v + } if v := f.PriceDefault; v != nil { e.PriceDefault = v } diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index e3136323f6..71da1df208 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -48,6 +48,7 @@ EIP1559DynamicFees = false FeeCapDefault = '100 gwei' TipCapDefault = '1' TipCapMin = '1' +EstimateGasLimit = false [GasEstimator.BlockHistory] BatchSize = 25 diff --git a/core/chains/evm/gas/helpers_test.go b/core/chains/evm/gas/helpers_test.go index 2c12ed426a..d6af645fe8 100644 --- a/core/chains/evm/gas/helpers_test.go +++ b/core/chains/evm/gas/helpers_test.go @@ -157,6 +157,7 @@ type MockGasEstimatorConfig struct { FeeCapDefaultF *assets.Wei LimitMaxF uint64 ModeF string + EstimateGasLimitF bool } func NewMockGasConfig() *MockGasEstimatorConfig { @@ -214,3 +215,7 @@ func (m *MockGasEstimatorConfig) LimitMax() uint64 { func (m *MockGasEstimatorConfig) Mode() string { return m.ModeF } + +func (m *MockGasEstimatorConfig) EstimateGasLimit() bool { + return m.EstimateGasLimitF +} diff --git a/core/chains/evm/gas/mocks/evm_fee_estimator.go b/core/chains/evm/gas/mocks/evm_fee_estimator.go index a9adc261ce..603115a94c 100644 --- a/core/chains/evm/gas/mocks/evm_fee_estimator.go +++ b/core/chains/evm/gas/mocks/evm_fee_estimator.go @@ -7,6 +7,8 @@ import ( assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + common "github.com/ethereum/go-ethereum/common" + context "context" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -145,14 +147,14 @@ func (_c *EvmFeeEstimator_Close_Call) RunAndReturn(run func() error) *EvmFeeEsti return _c } -// GetFee provides a mock function with given fields: ctx, calldata, feeLimit, maxFeePrice, opts -func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...types.Opt) (gas.EvmFee, uint64, error) { +// GetFee provides a mock function with given fields: ctx, calldata, feeLimit, maxFeePrice, toAddress, opts +func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...types.Opt) (gas.EvmFee, uint64, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] } var _ca []interface{} - _ca = append(_ca, ctx, calldata, feeLimit, maxFeePrice) + _ca = append(_ca, ctx, calldata, feeLimit, maxFeePrice, toAddress) _ca = append(_ca, _va...) ret := _m.Called(_ca...) @@ -163,23 +165,23 @@ func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit var r0 gas.EvmFee var r1 uint64 var r2 error - if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) (gas.EvmFee, uint64, error)); ok { - return rf(ctx, calldata, feeLimit, maxFeePrice, opts...) + if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) (gas.EvmFee, uint64, error)); ok { + return rf(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) gas.EvmFee); ok { - r0 = rf(ctx, calldata, feeLimit, maxFeePrice, opts...) + if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) gas.EvmFee); ok { + r0 = rf(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) } else { r0 = ret.Get(0).(gas.EvmFee) } - if rf, ok := ret.Get(1).(func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) uint64); ok { - r1 = rf(ctx, calldata, feeLimit, maxFeePrice, opts...) + if rf, ok := ret.Get(1).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) uint64); ok { + r1 = rf(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) } else { r1 = ret.Get(1).(uint64) } - if rf, ok := ret.Get(2).(func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) error); ok { - r2 = rf(ctx, calldata, feeLimit, maxFeePrice, opts...) + if rf, ok := ret.Get(2).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) error); ok { + r2 = rf(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) } else { r2 = ret.Error(2) } @@ -197,43 +199,44 @@ type EvmFeeEstimator_GetFee_Call struct { // - calldata []byte // - feeLimit uint64 // - maxFeePrice *assets.Wei +// - toAddress *common.Address // - opts ...types.Opt -func (_e *EvmFeeEstimator_Expecter) GetFee(ctx interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, opts ...interface{}) *EvmFeeEstimator_GetFee_Call { +func (_e *EvmFeeEstimator_Expecter) GetFee(ctx interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, toAddress interface{}, opts ...interface{}) *EvmFeeEstimator_GetFee_Call { return &EvmFeeEstimator_GetFee_Call{Call: _e.mock.On("GetFee", - append([]interface{}{ctx, calldata, feeLimit, maxFeePrice}, opts...)...)} + append([]interface{}{ctx, calldata, feeLimit, maxFeePrice, toAddress}, opts...)...)} } -func (_c *EvmFeeEstimator_GetFee_Call) Run(run func(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...types.Opt)) *EvmFeeEstimator_GetFee_Call { +func (_c *EvmFeeEstimator_GetFee_Call) Run(run func(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...types.Opt)) *EvmFeeEstimator_GetFee_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]types.Opt, len(args)-4) - for i, a := range args[4:] { + variadicArgs := make([]types.Opt, len(args)-5) + for i, a := range args[5:] { if a != nil { variadicArgs[i] = a.(types.Opt) } } - run(args[0].(context.Context), args[1].([]byte), args[2].(uint64), args[3].(*assets.Wei), variadicArgs...) + run(args[0].(context.Context), args[1].([]byte), args[2].(uint64), args[3].(*assets.Wei), args[4].(*common.Address), variadicArgs...) }) return _c } -func (_c *EvmFeeEstimator_GetFee_Call) Return(fee gas.EvmFee, chainSpecificFeeLimit uint64, err error) *EvmFeeEstimator_GetFee_Call { - _c.Call.Return(fee, chainSpecificFeeLimit, err) +func (_c *EvmFeeEstimator_GetFee_Call) Return(fee gas.EvmFee, estimatedFeeLimit uint64, err error) *EvmFeeEstimator_GetFee_Call { + _c.Call.Return(fee, estimatedFeeLimit, err) return _c } -func (_c *EvmFeeEstimator_GetFee_Call) RunAndReturn(run func(context.Context, []byte, uint64, *assets.Wei, ...types.Opt) (gas.EvmFee, uint64, error)) *EvmFeeEstimator_GetFee_Call { +func (_c *EvmFeeEstimator_GetFee_Call) RunAndReturn(run func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) (gas.EvmFee, uint64, error)) *EvmFeeEstimator_GetFee_Call { _c.Call.Return(run) return _c } -// GetMaxCost provides a mock function with given fields: ctx, amount, calldata, feeLimit, maxFeePrice, opts -func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...types.Opt) (*big.Int, error) { +// GetMaxCost provides a mock function with given fields: ctx, amount, calldata, feeLimit, maxFeePrice, toAddress, opts +func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...types.Opt) (*big.Int, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] } var _ca []interface{} - _ca = append(_ca, ctx, amount, calldata, feeLimit, maxFeePrice) + _ca = append(_ca, ctx, amount, calldata, feeLimit, maxFeePrice, toAddress) _ca = append(_ca, _va...) ret := _m.Called(_ca...) @@ -243,19 +246,19 @@ func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, ca var r0 *big.Int var r1 error - if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, ...types.Opt) (*big.Int, error)); ok { - return rf(ctx, amount, calldata, feeLimit, maxFeePrice, opts...) + if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) (*big.Int, error)); ok { + return rf(ctx, amount, calldata, feeLimit, maxFeePrice, toAddress, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, ...types.Opt) *big.Int); ok { - r0 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, opts...) + if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) *big.Int); ok { + r0 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, toAddress, opts...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*big.Int) } } - if rf, ok := ret.Get(1).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, ...types.Opt) error); ok { - r1 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, opts...) + if rf, ok := ret.Get(1).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) error); ok { + r1 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, toAddress, opts...) } else { r1 = ret.Error(1) } @@ -274,21 +277,22 @@ type EvmFeeEstimator_GetMaxCost_Call struct { // - calldata []byte // - feeLimit uint64 // - maxFeePrice *assets.Wei +// - toAddress *common.Address // - opts ...types.Opt -func (_e *EvmFeeEstimator_Expecter) GetMaxCost(ctx interface{}, amount interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, opts ...interface{}) *EvmFeeEstimator_GetMaxCost_Call { +func (_e *EvmFeeEstimator_Expecter) GetMaxCost(ctx interface{}, amount interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, toAddress interface{}, opts ...interface{}) *EvmFeeEstimator_GetMaxCost_Call { return &EvmFeeEstimator_GetMaxCost_Call{Call: _e.mock.On("GetMaxCost", - append([]interface{}{ctx, amount, calldata, feeLimit, maxFeePrice}, opts...)...)} + append([]interface{}{ctx, amount, calldata, feeLimit, maxFeePrice, toAddress}, opts...)...)} } -func (_c *EvmFeeEstimator_GetMaxCost_Call) Run(run func(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...types.Opt)) *EvmFeeEstimator_GetMaxCost_Call { +func (_c *EvmFeeEstimator_GetMaxCost_Call) Run(run func(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...types.Opt)) *EvmFeeEstimator_GetMaxCost_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]types.Opt, len(args)-5) - for i, a := range args[5:] { + variadicArgs := make([]types.Opt, len(args)-6) + for i, a := range args[6:] { if a != nil { variadicArgs[i] = a.(types.Opt) } } - run(args[0].(context.Context), args[1].(assets.Eth), args[2].([]byte), args[3].(uint64), args[4].(*assets.Wei), variadicArgs...) + run(args[0].(context.Context), args[1].(assets.Eth), args[2].([]byte), args[3].(uint64), args[4].(*assets.Wei), args[5].(*common.Address), variadicArgs...) }) return _c } @@ -298,7 +302,7 @@ func (_c *EvmFeeEstimator_GetMaxCost_Call) Return(_a0 *big.Int, _a1 error) *EvmF return _c } -func (_c *EvmFeeEstimator_GetMaxCost_Call) RunAndReturn(run func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, ...types.Opt) (*big.Int, error)) *EvmFeeEstimator_GetMaxCost_Call { +func (_c *EvmFeeEstimator_GetMaxCost_Call) RunAndReturn(run func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) (*big.Int, error)) *EvmFeeEstimator_GetMaxCost_Call { _c.Call.Return(run) return _c } diff --git a/core/chains/evm/gas/mocks/fee_estimator_client.go b/core/chains/evm/gas/mocks/fee_estimator_client.go index 8e10107597..ab99eb7b0d 100644 --- a/core/chains/evm/gas/mocks/fee_estimator_client.go +++ b/core/chains/evm/gas/mocks/fee_estimator_client.go @@ -241,6 +241,63 @@ func (_c *FeeEstimatorClient_ConfiguredChainID_Call) RunAndReturn(run func() *bi return _c } +// EstimateGas provides a mock function with given fields: ctx, call +func (_m *FeeEstimatorClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { + ret := _m.Called(ctx, call) + + if len(ret) == 0 { + panic("no return value specified for EstimateGas") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) (uint64, error)); ok { + return rf(ctx, call) + } + if rf, ok := ret.Get(0).(func(context.Context, ethereum.CallMsg) uint64); ok { + r0 = rf(ctx, call) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, ethereum.CallMsg) error); ok { + r1 = rf(ctx, call) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FeeEstimatorClient_EstimateGas_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateGas' +type FeeEstimatorClient_EstimateGas_Call struct { + *mock.Call +} + +// EstimateGas is a helper method to define mock.On call +// - ctx context.Context +// - call ethereum.CallMsg +func (_e *FeeEstimatorClient_Expecter) EstimateGas(ctx interface{}, call interface{}) *FeeEstimatorClient_EstimateGas_Call { + return &FeeEstimatorClient_EstimateGas_Call{Call: _e.mock.On("EstimateGas", ctx, call)} +} + +func (_c *FeeEstimatorClient_EstimateGas_Call) Run(run func(ctx context.Context, call ethereum.CallMsg)) *FeeEstimatorClient_EstimateGas_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(ethereum.CallMsg)) + }) + return _c +} + +func (_c *FeeEstimatorClient_EstimateGas_Call) Return(_a0 uint64, _a1 error) *FeeEstimatorClient_EstimateGas_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *FeeEstimatorClient_EstimateGas_Call) RunAndReturn(run func(context.Context, ethereum.CallMsg) (uint64, error)) *FeeEstimatorClient_EstimateGas_Call { + _c.Call.Return(run) + return _c +} + // HeadByNumber provides a mock function with given fields: ctx, n func (_m *FeeEstimatorClient) HeadByNumber(ctx context.Context, n *big.Int) (*types.Head, error) { ret := _m.Called(ctx, n) diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index c9607091fb..0da56a84d8 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -26,6 +26,9 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) +// EstimateGasBuffer is a multiplier applied to estimated gas when the EstimateGasLimit feature is enabled +const EstimateGasBuffer = float32(1.15) + // EvmFeeEstimator provides a unified interface that wraps EvmEstimator and can determine if legacy or dynamic fee estimation should be used type EvmFeeEstimator interface { services.Service @@ -33,11 +36,11 @@ type EvmFeeEstimator interface { // L1Oracle returns the L1 gas price oracle only if the chain has one, e.g. OP stack L2s and Arbitrum. L1Oracle() rollups.L1Oracle - GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) + GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) BumpFee(ctx context.Context, originalFee EvmFee, feeLimit uint64, maxFeePrice *assets.Wei, attempts []EvmPriorAttempt) (bumpedFee EvmFee, chainSpecificFeeLimit uint64, err error) // GetMaxCost returns the total value = max price x fee units + transferred value - GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (*big.Int, error) + GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (*big.Int, error) } type feeEstimatorClient interface { @@ -46,6 +49,7 @@ type feeEstimatorClient interface { CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error ConfiguredChainID() *big.Int HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error) + EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) } // NewEstimator returns the estimator for a given config @@ -70,6 +74,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, "tipCapMin", geCfg.TipCapMin(), "priceMax", geCfg.PriceMax(), "priceMin", geCfg.PriceMin(), + "estimateGasLimit", geCfg.EstimateGasLimit(), ) df := geCfg.EIP1559DynamicFees() @@ -111,7 +116,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, return NewFixedPriceEstimator(geCfg, ethClient, bh, lggr, l1Oracle) } } - return NewEvmFeeEstimator(lggr, newEstimator, df, geCfg), nil + return NewEvmFeeEstimator(lggr, newEstimator, df, geCfg, ethClient), nil } // DynamicFee encompasses both FeeCap and TipCap for EIP1559 transactions @@ -182,17 +187,19 @@ type evmFeeEstimator struct { EvmEstimator EIP1559Enabled bool geCfg GasEstimatorConfig + ethClient feeEstimatorClient } var _ EvmFeeEstimator = (*evmFeeEstimator)(nil) -func NewEvmFeeEstimator(lggr logger.Logger, newEstimator func(logger.Logger) EvmEstimator, eip1559Enabled bool, geCfg GasEstimatorConfig) EvmFeeEstimator { +func NewEvmFeeEstimator(lggr logger.Logger, newEstimator func(logger.Logger) EvmEstimator, eip1559Enabled bool, geCfg GasEstimatorConfig, ethClient feeEstimatorClient) EvmFeeEstimator { lggr = logger.Named(lggr, "WrappedEvmEstimator") return &evmFeeEstimator{ lggr: lggr, EvmEstimator: newEstimator(lggr), EIP1559Enabled: eip1559Enabled, geCfg: geCfg, + ethClient: ethClient, } } @@ -262,7 +269,10 @@ func (e *evmFeeEstimator) L1Oracle() rollups.L1Oracle { return e.EvmEstimator.L1Oracle() } -func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) { +// GetFee returns an initial estimated gas price and gas limit for a transaction +// The gas limit provided by the caller can be adjusted by gas estimation or for 2D fees +func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) { + var chainSpecificFeeLimit uint64 // get dynamic fee if e.EIP1559Enabled { var dynamicFee DynamicFee @@ -270,24 +280,23 @@ func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit if err != nil { return } - chainSpecificFeeLimit, err = commonfee.ApplyMultiplier(feeLimit, e.geCfg.LimitMultiplier()) fee.DynamicFeeCap = dynamicFee.FeeCap fee.DynamicTipCap = dynamicFee.TipCap - return - } - - // get legacy fee - fee.Legacy, chainSpecificFeeLimit, err = e.EvmEstimator.GetLegacyGas(ctx, calldata, feeLimit, maxFeePrice, opts...) - if err != nil { - return + chainSpecificFeeLimit = feeLimit + } else { + // get legacy fee + fee.Legacy, chainSpecificFeeLimit, err = e.EvmEstimator.GetLegacyGas(ctx, calldata, feeLimit, maxFeePrice, opts...) + if err != nil { + return + } } - chainSpecificFeeLimit, err = commonfee.ApplyMultiplier(chainSpecificFeeLimit, e.geCfg.LimitMultiplier()) + estimatedFeeLimit, err = e.estimateFeeLimit(ctx, chainSpecificFeeLimit, calldata, toAddress) return } -func (e *evmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (*big.Int, error) { - fees, gasLimit, err := e.GetFee(ctx, calldata, feeLimit, maxFeePrice, opts...) +func (e *evmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (*big.Int, error) { + fees, gasLimit, err := e.GetFee(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) if err != nil { return nil, err } @@ -338,6 +347,53 @@ func (e *evmFeeEstimator) BumpFee(ctx context.Context, originalFee EvmFee, feeLi return } +func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, feeLimit uint64, calldata []byte, toAddress *common.Address) (estimatedFeeLimit uint64, err error) { + // Use the feeLimit * LimitMultiplier as the provided gas limit since this multiplier is applied on top of the caller specified gas limit + providedGasLimit, err := commonfee.ApplyMultiplier(feeLimit, e.geCfg.LimitMultiplier()) + if err != nil { + return estimatedFeeLimit, err + } + // Use provided fee limit by default if EstimateGasLimit is disabled + if !e.geCfg.EstimateGasLimit() { + return providedGasLimit, nil + } + // Create call msg for gas limit estimation + // Skip setting Gas to avoid capping the results of the estimation + callMsg := ethereum.CallMsg{ + To: toAddress, + Data: calldata, + } + estimatedGas, estimateErr := e.ethClient.EstimateGas(ctx, callMsg) + if estimateErr != nil { + if providedGasLimit > 0 { + // Do not return error if estimate gas failed, we can still use the provided limit instead since it is an upper limit + e.lggr.Errorw("failed to estimate gas limit. falling back to the provided gas limit with multiplier", "callMsg", callMsg, "providedGasLimitWithMultiplier", providedGasLimit, "error", estimateErr) + return providedGasLimit, nil + } + return estimatedFeeLimit, fmt.Errorf("gas estimation failed and provided gas limit is 0: %w", estimateErr) + } + e.lggr.Debugw("estimated gas", "estimatedGas", estimatedGas, "providedGasLimitWithMultiplier", providedGasLimit) + // Return error if estimated gas without the buffer exceeds the provided gas limit, if provided + // Transaction would be destined to run out of gas and fail + if providedGasLimit > 0 && estimatedGas > providedGasLimit { + e.lggr.Errorw("estimated gas exceeds provided gas limit with multiplier", "estimatedGas", estimatedGas, "providedGasLimitWithMultiplier", providedGasLimit) + return estimatedFeeLimit, commonfee.ErrFeeLimitTooLow + } + // Apply EstimateGasBuffer to the estimated gas limit + estimatedFeeLimit, err = commonfee.ApplyMultiplier(estimatedGas, EstimateGasBuffer) + if err != nil { + return + } + // If provided gas limit is not 0, fallback to it if the buffer causes the estimated gas limit to exceed it + // The provided gas limit should be used as an upper bound to avoid unexpected behavior for products + if providedGasLimit > 0 && estimatedFeeLimit > providedGasLimit { + e.lggr.Debugw("estimated gas limit with buffer exceeds the provided gas limit with multiplier. falling back to the provided gas limit with multiplier", "estimatedGasLimit", estimatedFeeLimit, "providedGasLimitWithMultiplier", providedGasLimit) + estimatedFeeLimit = providedGasLimit + } + + return +} + // Config defines an interface for configuration in the gas package type Config interface { ChainType() chaintype.ChainType @@ -359,6 +415,7 @@ type GasEstimatorConfig interface { PriceMin() *assets.Wei PriceMax() *assets.Wei Mode() string + EstimateGasLimit() bool } type BlockHistoryConfig interface { diff --git a/core/chains/evm/gas/models_test.go b/core/chains/evm/gas/models_test.go index 92ea901596..14ef085497 100644 --- a/core/chains/evm/gas/models_test.go +++ b/core/chains/evm/gas/models_test.go @@ -1,6 +1,7 @@ package gas_test import ( + "errors" "math/big" "testing" @@ -12,12 +13,14 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" rollupMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils" ) func TestWrappedEvmEstimator(t *testing.T) { @@ -35,9 +38,9 @@ func TestWrappedEvmEstimator(t *testing.T) { est := mocks.NewEvmEstimator(t) est.On("GetDynamicFee", mock.Anything, mock.Anything). - Return(dynamicFee, nil).Twice() + Return(dynamicFee, nil).Times(6) est.On("GetLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(legacyFee, gasLimit, nil).Twice() + Return(legacyFee, gasLimit, nil).Times(6) est.On("BumpDynamicFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(dynamicFee, nil).Once() est.On("BumpLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). @@ -59,7 +62,7 @@ func TestWrappedEvmEstimator(t *testing.T) { getEst := func(logger.Logger) gas.EvmEstimator { return evmEstimator } // expect nil - estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, nil) + estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, nil, nil) l1Oracle := estimator.L1Oracle() assert.Nil(t, l1Oracle) @@ -68,7 +71,7 @@ func TestWrappedEvmEstimator(t *testing.T) { oracle, err := rollups.NewL1GasOracle(lggr, nil, chaintype.ChainOptimismBedrock) require.NoError(t, err) // cast oracle to L1Oracle interface - estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg) + estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) evmEstimator.On("L1Oracle").Return(oracle).Once() l1Oracle = estimator.L1Oracle() @@ -80,8 +83,8 @@ func TestWrappedEvmEstimator(t *testing.T) { lggr := logger.Test(t) // expect legacy fee data dynamicFees := false - estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg) - fee, max, err := estimator.GetFee(ctx, nil, 0, nil) + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) + fee, max, err := estimator.GetFee(ctx, nil, 0, nil, nil) require.NoError(t, err) assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), max) assert.True(t, legacyFee.Equal(fee.Legacy)) @@ -90,8 +93,8 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect dynamic fee data dynamicFees = true - estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg) - fee, max, err = estimator.GetFee(ctx, nil, gasLimit, nil) + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) + fee, max, err = estimator.GetFee(ctx, nil, gasLimit, nil, nil) require.NoError(t, err) assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), max) assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) @@ -103,7 +106,7 @@ func TestWrappedEvmEstimator(t *testing.T) { t.Run("BumpFee", func(t *testing.T) { lggr := logger.Test(t) dynamicFees := false - estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg) + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) // expect legacy fee data fee, max, err := estimator.BumpFee(ctx, gas.EvmFee{Legacy: assets.NewWeiI(0)}, 0, nil, nil) @@ -141,8 +144,8 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect legacy fee data dynamicFees := false - estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg) - total, err := estimator.GetMaxCost(ctx, val, nil, gasLimit, nil) + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) + total, err := estimator.GetMaxCost(ctx, val, nil, gasLimit, nil, nil) require.NoError(t, err) fee := new(big.Int).Mul(legacyFee.ToInt(), big.NewInt(int64(gasLimit))) fee, _ = new(big.Float).Mul(new(big.Float).SetInt(fee), big.NewFloat(float64(limitMultiplier))).Int(nil) @@ -150,8 +153,8 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect dynamic fee data dynamicFees = true - estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg) - total, err = estimator.GetMaxCost(ctx, val, nil, gasLimit, nil) + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) + total, err = estimator.GetMaxCost(ctx, val, nil, gasLimit, nil, nil) require.NoError(t, err) fee = new(big.Int).Mul(dynamicFee.FeeCap.ToInt(), big.NewInt(int64(gasLimit))) fee, _ = new(big.Float).Mul(new(big.Float).SetInt(fee), big.NewFloat(float64(limitMultiplier))).Int(nil) @@ -166,7 +169,7 @@ func TestWrappedEvmEstimator(t *testing.T) { estimator := gas.NewEvmFeeEstimator(lggr, func(logger.Logger) gas.EvmEstimator { return evmEstimator - }, false, geCfg) + }, false, geCfg, nil) require.Equal(t, mockEstimatorName, estimator.Name()) require.Equal(t, mockEvmEstimatorName, evmEstimator.Name()) @@ -185,7 +188,7 @@ func TestWrappedEvmEstimator(t *testing.T) { evmEstimator.On("L1Oracle", mock.Anything).Return(nil).Twice() - estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg) + estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) err := estimator.Start(ctx) require.NoError(t, err) err = estimator.Close() @@ -193,7 +196,7 @@ func TestWrappedEvmEstimator(t *testing.T) { evmEstimator.On("L1Oracle", mock.Anything).Return(oracle).Twice() - estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg) + estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) err = estimator.Start(ctx) require.NoError(t, err) err = estimator.Close() @@ -210,11 +213,11 @@ func TestWrappedEvmEstimator(t *testing.T) { oracle.On("Ready").Return(nil).Twice() getEst := func(logger.Logger) gas.EvmEstimator { return evmEstimator } - estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg) + estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) err := estimator.Ready() require.NoError(t, err) - estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg) + estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) err = estimator.Ready() require.NoError(t, err) }) @@ -235,7 +238,7 @@ func TestWrappedEvmEstimator(t *testing.T) { oracle.On("HealthReport").Return(map[string]error{oracleKey: oracleError}).Once() getEst := func(logger.Logger) gas.EvmEstimator { return evmEstimator } - estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg) + estimator := gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) report := estimator.HealthReport() require.True(t, pkgerrors.Is(report[evmEstimatorKey], evmEstimatorError)) require.Nil(t, report[oracleKey]) @@ -243,10 +246,166 @@ func TestWrappedEvmEstimator(t *testing.T) { evmEstimator.On("L1Oracle").Return(oracle).Once() - estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg) + estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) report = estimator.HealthReport() require.True(t, pkgerrors.Is(report[evmEstimatorKey], evmEstimatorError)) require.True(t, pkgerrors.Is(report[oracleKey], oracleError)) require.NotNil(t, report[mockEstimatorName]) }) + + t.Run("GetFee, estimate gas limit enabled, succeeds", func(t *testing.T) { + estimatedGasLimit := uint64(5) + lggr := logger.Test(t) + // expect legacy fee data + dynamicFees := false + geCfg.EstimateGasLimitF = true + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + toAddress := testutils.NewAddress() + fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit) + assert.True(t, legacyFee.Equal(fee.Legacy)) + assert.Nil(t, fee.DynamicTipCap) + assert.Nil(t, fee.DynamicFeeCap) + + // expect dynamic fee data + dynamicFees = true + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit) + assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) + assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap)) + assert.Nil(t, fee.Legacy) + }) + + t.Run("GetFee, estimate gas limit enabled, estimate exceeds provided limit, returns error", func(t *testing.T) { + estimatedGasLimit := uint64(100) + lggr := logger.Test(t) + // expect legacy fee data + dynamicFees := false + geCfg.EstimateGasLimitF = true + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + toAddress := testutils.NewAddress() + _, _, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.ErrorIs(t, err, commonfee.ErrFeeLimitTooLow) + + // expect dynamic fee data + dynamicFees = true + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + _, _, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.ErrorIs(t, err, commonfee.ErrFeeLimitTooLow) + }) + + t.Run("GetFee, estimate gas limit enabled, buffer exceeds provided limit, fallsback to provided limit", func(t *testing.T) { + estimatedGasLimit := uint64(15) // same as provided limit + lggr := logger.Test(t) + dynamicFees := false // expect legacy fee data + geCfg.EstimateGasLimitF = true + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + toAddress := testutils.NewAddress() + fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) + assert.True(t, legacyFee.Equal(fee.Legacy)) + assert.Nil(t, fee.DynamicTipCap) + assert.Nil(t, fee.DynamicFeeCap) + + dynamicFees = true // expect dynamic fee data + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) + assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) + assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap)) + assert.Nil(t, fee.Legacy) + }) + + t.Run("GetFee, estimate gas limit enabled, RPC fails and fallsback to provided gas limit", func(t *testing.T) { + lggr := logger.Test(t) + // expect legacy fee data + dynamicFees := false + geCfg.EstimateGasLimitF = true + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(0), errors.New("something broke")).Twice() + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + toAddress := testutils.NewAddress() + fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) + assert.True(t, legacyFee.Equal(fee.Legacy)) + assert.Nil(t, fee.DynamicTipCap) + assert.Nil(t, fee.DynamicFeeCap) + + // expect dynamic fee data + dynamicFees = true + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) + assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) + assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap)) + assert.Nil(t, fee.Legacy) + }) + + t.Run("GetFee, estimate gas limit enabled, provided fee limit 0, returns uncapped estimation", func(t *testing.T) { + est.On("GetDynamicFee", mock.Anything, mock.Anything). + Return(dynamicFee, nil).Once() + est.On("GetLegacyGas", mock.Anything, mock.Anything, uint64(0), mock.Anything). + Return(legacyFee, uint64(0), nil).Once() + estimatedGasLimit := uint64(100) // same as provided limit + lggr := logger.Test(t) + // expect legacy fee data + dynamicFees := false + geCfg.EstimateGasLimitF = true + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + toAddress := testutils.NewAddress() + fee, limit, err := estimator.GetFee(ctx, []byte{}, uint64(0), nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit) + assert.True(t, legacyFee.Equal(fee.Legacy)) + assert.Nil(t, fee.DynamicTipCap) + assert.Nil(t, fee.DynamicFeeCap) + + // expect dynamic fee data + dynamicFees = true + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + fee, limit, err = estimator.GetFee(ctx, []byte{}, 0, nil, &toAddress) + require.NoError(t, err) + assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit) + assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) + assert.True(t, dynamicFee.TipCap.Equal(fee.DynamicTipCap)) + assert.Nil(t, fee.Legacy) + }) + + t.Run("GetFee, estimate gas limit enabled, provided fee limit 0, returns error on failure", func(t *testing.T) { + est.On("GetDynamicFee", mock.Anything, mock.Anything). + Return(dynamicFee, nil).Once() + est.On("GetLegacyGas", mock.Anything, mock.Anything, uint64(0), mock.Anything). + Return(legacyFee, uint64(0), nil).Once() + lggr := logger.Test(t) + // expect legacy fee data + dynamicFees := false + geCfg.EstimateGasLimitF = true + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(0), errors.New("something broke")).Twice() + estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + toAddress := testutils.NewAddress() + _, _, err := estimator.GetFee(ctx, []byte{}, 0, nil, &toAddress) + require.Error(t, err) + + // expect dynamic fee data + dynamicFees = true + estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) + _, _, err = estimator.GetFee(ctx, []byte{}, 0, nil, &toAddress) + require.Error(t, err) + }) } diff --git a/core/chains/evm/txmgr/attempts.go b/core/chains/evm/txmgr/attempts.go index 8566adcb5c..c57ecc4412 100644 --- a/core/chains/evm/txmgr/attempts.go +++ b/core/chains/evm/txmgr/attempts.go @@ -58,7 +58,7 @@ func (c *evmTxAttemptBuilder) NewTxAttempt(ctx context.Context, etx Tx, lggr log // used for L2 re-estimation on broadcasting (note EIP1559 must be disabled otherwise this will fail with mismatched fees + tx type) func (c *evmTxAttemptBuilder) NewTxAttemptWithType(ctx context.Context, etx Tx, lggr logger.Logger, txType int, opts ...feetypes.Opt) (attempt TxAttempt, fee gas.EvmFee, feeLimit uint64, retryable bool, err error) { keySpecificMaxGasPriceWei := c.feeConfig.PriceMaxKey(etx.FromAddress) - fee, feeLimit, err = c.EvmFeeEstimator.GetFee(ctx, etx.EncodedPayload, etx.FeeLimit, keySpecificMaxGasPriceWei, opts...) + fee, feeLimit, err = c.EvmFeeEstimator.GetFee(ctx, etx.EncodedPayload, etx.FeeLimit, keySpecificMaxGasPriceWei, &etx.ToAddress, opts...) if err != nil { return attempt, fee, feeLimit, true, pkgerrors.Wrap(err, "failed to get fee") // estimator errors are retryable } @@ -71,8 +71,8 @@ func (c *evmTxAttemptBuilder) NewTxAttemptWithType(ctx context.Context, etx Tx, // used in the txm broadcaster + confirmer when tx ix rejected for too low fee or is not included in a timely manner func (c *evmTxAttemptBuilder) NewBumpTxAttempt(ctx context.Context, etx Tx, previousAttempt TxAttempt, priorAttempts []TxAttempt, lggr logger.Logger) (attempt TxAttempt, bumpedFee gas.EvmFee, bumpedFeeLimit uint64, retryable bool, err error) { keySpecificMaxGasPriceWei := c.feeConfig.PriceMaxKey(etx.FromAddress) - - bumpedFee, bumpedFeeLimit, err = c.EvmFeeEstimator.BumpFee(ctx, previousAttempt.TxFee, etx.FeeLimit, keySpecificMaxGasPriceWei, newEvmPriorAttempts(priorAttempts)) + // Use the fee limit from the previous attempt to maintain limits adjusted for 2D fees or by estimation + bumpedFee, bumpedFeeLimit, err = c.EvmFeeEstimator.BumpFee(ctx, previousAttempt.TxFee, previousAttempt.ChainSpecificFeeLimit, keySpecificMaxGasPriceWei, newEvmPriorAttempts(priorAttempts)) if err != nil { return attempt, bumpedFee, bumpedFeeLimit, true, pkgerrors.Wrap(err, "failed to bump fee") // estimator errors are retryable } diff --git a/core/chains/evm/txmgr/attempts_test.go b/core/chains/evm/txmgr/attempts_test.go index 6be8cd7067..ea00f7a347 100644 --- a/core/chains/evm/txmgr/attempts_test.go +++ b/core/chains/evm/txmgr/attempts_test.go @@ -339,7 +339,7 @@ func TestTxm_NewCustomTxAttempt_NonRetryableErrors(t *testing.T) { func TestTxm_EvmTxAttemptBuilder_RetryableEstimatorError(t *testing.T) { est := gasmocks.NewEvmFeeEstimator(t) - est.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail")) + est.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail")) est.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail")) kst := ksmocks.NewEth(t) diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index cbbe522ba1..343988196c 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -29,8 +29,10 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + commmonfee "github.com/smartcontractkit/chainlink/v2/common/fee" txmgrcommon "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" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" @@ -69,7 +71,7 @@ func NewTestEthBroadcaster( estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator { return gas.NewFixedPriceEstimator(config.EVM().GasEstimator(), nil, ge.BlockHistory(), lggr, nil) - }, ge.EIP1559DynamicFees(), ge) + }, ge.EIP1559DynamicFees(), ge, ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, keyStore, estimator) ethBroadcaster := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), gconfig.Database().Listener(), keyStore, txBuilder, nonceTracker, lggr, checkerFactory, nonceAutoSync, "") @@ -642,7 +644,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi chStartEstimate := make(chan struct{}) chBlock := make(chan struct{}) - estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, ccfg.EVM().GasEstimator().PriceMaxKey(fromAddress)).Return(gas.EvmFee{Legacy: assets.GWei(32)}, uint64(500), nil).Run(func(_ mock.Arguments) { + estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, ccfg.EVM().GasEstimator().PriceMaxKey(fromAddress), mock.Anything).Return(gas.EvmFee{Legacy: assets.GWei(32)}, uint64(500), nil).Run(func(_ mock.Arguments) { close(chStartEstimate) <-chBlock }).Once() @@ -1177,7 +1179,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { t.Run("callback set by ctor", func(t *testing.T) { estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator { return gas.NewFixedPriceEstimator(evmcfg.EVM().GasEstimator(), nil, evmcfg.EVM().GasEstimator().BlockHistory(), lggr, nil) - }, evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), evmcfg.EVM().GasEstimator()) + }, evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), evmcfg.EVM().GasEstimator(), ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator) localNextNonce = getLocalNextNonce(t, nonceTracker, fromAddress) eb2 := txmgr.NewEvmBroadcaster(txStore, txmClient, txmgr.NewEvmTxmConfig(evmcfg.EVM()), txmgr.NewEvmTxmFeeConfig(evmcfg.EVM().GasEstimator()), evmcfg.EVM().Transactions(), cfg.Database().Listener(), ethKeyStore, txBuilder, lggr, &testCheckerFactory{}, false, "") @@ -1666,6 +1668,73 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { }) } +func TestEthBroadcaster_ProcessUnstartedEthTxs_GasEstimationError(t *testing.T) { + toAddress := testutils.NewAddress() + value := big.Int(assets.NewEthValue(142)) + gasLimit := uint64(242) + encodedPayload := []byte{0, 1} + + db := pgtest.NewSqlxDB(t) + cfg := configtest.NewTestGeneralConfig(t) + cfg.EVMConfigs()[0].GasEstimator.EstimateGasLimit = ptr(true) // Enabled gas limit estimation + limitMultiplier := float32(1.25) + cfg.EVMConfigs()[0].GasEstimator.LimitMultiplier = ptr(decimal.NewFromFloat32(limitMultiplier)) // Set LimitMultiplier for the buffer + txStore := cltest.NewTestTxStore(t, db) + + ethKeyStore := cltest.NewKeyStore(t, db).Eth() + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + + config := evmtest.NewChainScopedConfig(t, cfg) + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + lggr := logger.Test(t) + txmClient := txmgr.NewEvmTxmClient(ethClient, nil) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmClient) + ge := config.EVM().GasEstimator() + estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator { + return gas.NewFixedPriceEstimator(ge, nil, ge.BlockHistory(), lggr, nil) + }, ge.EIP1559DynamicFees(), ge, ethClient) + txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, estimator) + eb := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), cfg.Database().Listener(), ethKeyStore, txBuilder, nonceTracker, lggr, &testCheckerFactory{}, false, "") + + // Mark instance as test + eb.XXXTestDisableUnstartedTxAutoProcessing() + servicetest.Run(t, eb) + ctx := tests.Context(t) + t.Run("gas limit lowered after estimation", func(t *testing.T) { + estimatedGasLimit := uint64(100) + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Once() + ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { + return tx.Nonce() == uint64(0) + }), fromAddress).Return(commonclient.Successful, nil).Once() + + // Do the thing + retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) + assert.NoError(t, err) + assert.False(t, retryable) + + dbEtx, err := txStore.FindTxWithAttempts(ctx, etx.ID) + require.NoError(t, err) + attempt := dbEtx.TxAttempts[0] + require.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), attempt.ChainSpecificFeeLimit) + }) + t.Run("provided gas limit too low, transaction marked as fatal error", func(t *testing.T) { + etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID) + ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(float32(gasLimit)*limitMultiplier)+1, nil).Once() + + // Do the thing + retryable, err := eb.ProcessUnstartedTxs(ctx, fromAddress) + assert.NoError(t, err) + assert.False(t, retryable) + + dbEtx, err := txStore.FindTxWithAttempts(ctx, etx.ID) + require.NoError(t, err) + require.Equal(t, txmgrcommon.TxFatalError, dbEtx.State) + require.Equal(t, commmonfee.ErrFeeLimitTooLow.Error(), dbEtx.Error.String) + }) +} + func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { toAddress := gethCommon.HexToAddress("0x6C03DDA95a2AEd917EeCc6eddD4b9D16E6380411") value := big.Int(assets.NewEthValue(142)) @@ -1760,15 +1829,15 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) { kst := cltest.NewKeyStore(t, db).Eth() _, fromAddress := cltest.RandomKey{Disabled: false}.MustInsertWithState(t, kst) + ethClient := testutils.NewEthClientMockWithDefaultChain(t) estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator { return gas.NewFixedPriceEstimator(evmcfg.EVM().GasEstimator(), nil, evmcfg.EVM().GasEstimator().BlockHistory(), lggr, nil) - }, evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), evmcfg.EVM().GasEstimator()) + }, evmcfg.EVM().GasEstimator().EIP1559DynamicFees(), evmcfg.EVM().GasEstimator(), ethClient) checkerFactory := &testCheckerFactory{} ge := evmcfg.EVM().GasEstimator() t.Run("does nothing if nonce sync is disabled", func(t *testing.T) { - ethClient := testutils.NewEthClientMockWithDefaultChain(t) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, kst, estimator) kst := ksmocks.NewEth(t) @@ -1838,7 +1907,7 @@ func TestEthBroadcaster_HederaBroadcastValidation(t *testing.T) { ge := evmcfg.EVM().GasEstimator() estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator { return gas.NewFixedPriceEstimator(evmcfg.EVM().GasEstimator(), nil, ge.BlockHistory(), lggr, nil) - }, ge.EIP1559DynamicFees(), ge) + }, ge.EIP1559DynamicFees(), ge, ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, estimator) checkerFactory := &txmgr.CheckerFactory{Client: ethClient} ctx := tests.Context(t) diff --git a/core/chains/evm/txmgr/confirmer_test.go b/core/chains/evm/txmgr/confirmer_test.go index 45d7ac9e12..daba2e8d92 100644 --- a/core/chains/evm/txmgr/confirmer_test.go +++ b/core/chains/evm/txmgr/confirmer_test.go @@ -128,7 +128,7 @@ func TestEthConfirmer_Lifecycle(t *testing.T) { newEst := func(logger.Logger) gas.EvmEstimator { return estimator } lggr := logger.Test(t) ge := config.EVM().GasEstimator() - feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge) + feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge, ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, feeEstimator) stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), config.EVM().Transactions().AutoPurge(), feeEstimator, txStore, ethClient) ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), gconfig.Database(), ethKeyStore, txBuilder, lggr, stuckTxDetector) @@ -1646,7 +1646,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing newEst := func(logger.Logger) gas.EvmEstimator { return estimator } estimator.On("BumpLegacyGas", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, uint64(0), pkgerrors.Wrapf(commonfee.ErrConnectivity, "transaction...")) ge := ccfg.EVM().GasEstimator() - feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge) + feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge, ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, kst, feeEstimator) addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe() @@ -1695,7 +1695,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing newEst := func(logger.Logger) gas.EvmEstimator { return estimator } // Create confirmer with necessary state ge := ccfg.EVM().GasEstimator() - feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge) + feeEstimator := gas.NewEvmFeeEstimator(lggr, newEst, ge.EIP1559DynamicFees(), ge, ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, kst, feeEstimator) addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe() @@ -3195,7 +3195,7 @@ func TestEthConfirmer_ProcessStuckTransactions(t *testing.T) { fee := gas.EvmFee{Legacy: marketGasPrice} bumpedLegacy := assets.GWei(30) bumpedFee := gas.EvmFee{Legacy: bumpedLegacy} - feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything).Return(fee, uint64(0), nil) + feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything).Return(fee, uint64(0), nil) feeEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(bumpedFee, uint64(10_000), nil) autoPurgeThreshold := uint32(5) autoPurgeMinAttempts := uint32(3) @@ -3286,7 +3286,7 @@ func newEthConfirmer(t testing.TB, txStore txmgr.EvmTxStore, ethClient client.Cl ge := config.EVM().GasEstimator() estimator := gas.NewEvmFeeEstimator(lggr, func(lggr logger.Logger) gas.EvmEstimator { return gas.NewFixedPriceEstimator(ge, nil, ge.BlockHistory(), lggr, nil) - }, ge.EIP1559DynamicFees(), ge) + }, ge.EIP1559DynamicFees(), ge, ethClient) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ks, estimator) stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, "", assets.NewWei(assets.NewEth(100).ToInt()), config.EVM().Transactions().AutoPurge(), estimator, txStore, ethClient) ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, nil), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), gconfig.Database(), ks, txBuilder, lggr, stuckTxDetector) diff --git a/core/chains/evm/txmgr/stuck_tx_detector.go b/core/chains/evm/txmgr/stuck_tx_detector.go index 5d621dc0b2..4e521d5f8f 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector.go +++ b/core/chains/evm/txmgr/stuck_tx_detector.go @@ -25,7 +25,7 @@ import ( ) type stuckTxDetectorGasEstimator interface { - GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (fee gas.EvmFee, chainSpecificFeeLimit uint64, err error) + GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee gas.EvmFee, chainSpecificFeeLimit uint64, err error) } type stuckTxDetectorClient interface { @@ -199,7 +199,7 @@ func (d *stuckTxDetector) detectStuckTransactionsHeuristic(ctx context.Context, defer d.purgeBlockNumLock.RUnlock() // Get gas price from internal gas estimator // Send with max gas price time 2 to prevent the results from being capped. Need the market gas price here. - marketGasPrice, _, err := d.gasEstimator.GetFee(ctx, []byte{}, 0, d.maxPrice.Mul(big.NewInt(2))) + marketGasPrice, _, err := d.gasEstimator.GetFee(ctx, []byte{}, 0, d.maxPrice.Mul(big.NewInt(2)), nil) if err != nil { return txs, fmt.Errorf("failed to get market gas price for overflow detection: %w", err) } diff --git a/core/chains/evm/txmgr/stuck_tx_detector_test.go b/core/chains/evm/txmgr/stuck_tx_detector_test.go index def49f8e11..5e022091a6 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector_test.go +++ b/core/chains/evm/txmgr/stuck_tx_detector_test.go @@ -73,7 +73,7 @@ func TestStuckTxDetector_LoadPurgeBlockNumMap(t *testing.T) { feeEstimator := gasmocks.NewEvmFeeEstimator(t) marketGasPrice := assets.GWei(15) fee := gas.EvmFee{Legacy: marketGasPrice} - feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything).Return(fee, uint64(0), nil) + feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything).Return(fee, uint64(0), nil) autoPurgeThreshold := uint32(5) autoPurgeMinAttempts := uint32(3) autoPurgeCfg := testAutoPurgeConfig{ @@ -194,7 +194,7 @@ func TestStuckTxDetector_DetectStuckTransactionsHeuristic(t *testing.T) { // Return 10 gwei as market gas price marketGasPrice := tenGwei fee := gas.EvmFee{Legacy: marketGasPrice} - feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything).Return(fee, uint64(0), nil) + feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything).Return(fee, uint64(0), nil) ethClient := testutils.NewEthClientMockWithDefaultChain(t) autoPurgeThreshold := uint32(5) autoPurgeMinAttempts := uint32(3) diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index 3b3584a988..a4f326d480 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -89,6 +89,7 @@ func (g *TestGasEstimatorConfig) LimitTransfer() uint64 { return 42 } func (g *TestGasEstimatorConfig) PriceMax() *assets.Wei { return assets.NewWeiI(42) } func (g *TestGasEstimatorConfig) PriceMin() *assets.Wei { return assets.NewWeiI(42) } func (g *TestGasEstimatorConfig) Mode() string { return "FixedPrice" } +func (g *TestGasEstimatorConfig) EstimateGasLimit() bool { return false } func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType { return &TestLimitJobTypeConfig{} } diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index 73ea0ebb35..3e54d2cd0f 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -194,6 +194,8 @@ LimitMax = 8_000_000 # Default LimitMultiplier = '1.0' # Default # LimitTransfer is the gas limit used for an ordinary ETH transfer. LimitTransfer = 21_000 # Default +# EstimateGasLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound. +EstimateGasLimit = false # Default # BumpMin is the minimum fixed amount of wei by which gas is bumped on each transaction attempt. BumpMin = '5 gwei' # Default # BumpPercent is the percentage by which to bump gas on a transaction that has exceeded `BumpThreshold`. The larger of `BumpPercent` and `BumpMin` is taken for gas bumps. diff --git a/core/internal/features/features_test.go b/core/internal/features/features_test.go index 1259597379..3ce14ef284 100644 --- a/core/internal/features/features_test.go +++ b/core/internal/features/features_test.go @@ -1339,7 +1339,7 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) { chain := evmtest.MustGetDefaultChain(t, legacyChains) estimator := chain.GasEstimator() - gasPrice, gasLimit, err := estimator.GetFee(testutils.Context(t), nil, 500_000, maxGasPrice) + gasPrice, gasLimit, err := estimator.GetFee(testutils.Context(t), nil, 500_000, maxGasPrice, nil) require.NoError(t, err) assert.Equal(t, uint64(500000), gasLimit) assert.Equal(t, "41.5 gwei", gasPrice.Legacy.String()) @@ -1360,7 +1360,7 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) { newHeads.TrySend(h43) gomega.NewWithT(t).Eventually(func() string { - gasPrice, _, err := estimator.GetFee(testutils.Context(t), nil, 500000, maxGasPrice) + gasPrice, _, err := estimator.GetFee(testutils.Context(t), nil, 500000, maxGasPrice, nil) require.NoError(t, err) return gasPrice.Legacy.String() }, testutils.WaitTimeout(t), cltest.DBPollingInterval).Should(gomega.Equal("45 gwei")) diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index e9ed223b9b..3ded57cf76 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -520,6 +520,7 @@ func TestConfig_Marshal(t *testing.T) { LimitMax: ptr[uint64](17), LimitMultiplier: mustDecimal("1.234"), LimitTransfer: ptr[uint64](100), + EstimateGasLimit: ptr(false), TipCapDefault: assets.NewWeiI(2), TipCapMin: assets.NewWeiI(1), PriceDefault: assets.NewWeiI(math.MaxInt64), @@ -1024,6 +1025,7 @@ LimitDefault = 12 LimitMax = 17 LimitMultiplier = '1.234' LimitTransfer = 100 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 10 BumpThreshold = 6 diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index 9df2e70508..ea62617018 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -316,6 +316,7 @@ LimitDefault = 12 LimitMax = 17 LimitMultiplier = '1.234' LimitTransfer = 100 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 10 BumpThreshold = 6 diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index e560099c2c..dacb04fe9d 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -303,6 +303,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -403,6 +404,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -497,6 +499,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 diff --git a/core/services/keeper/upkeep_executer_test.go b/core/services/keeper/upkeep_executer_test.go index c70a92c725..0f68543904 100644 --- a/core/services/keeper/upkeep_executer_test.go +++ b/core/services/keeper/upkeep_executer_test.go @@ -48,7 +48,7 @@ func mockEstimator(t *testing.T) gas.EvmFeeEstimator { // note: estimator will only return 1 of legacy or dynamic fees (not both) // assumed to call legacy estimator only estimator := gasmocks.NewEvmFeeEstimator(t) - estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(gas.EvmFee{ + estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(gas.EvmFee{ Legacy: assets.GWei(60), }, uint32(60), nil) return estimator diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go index 7f6b1c1bb7..ad85b6e682 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go @@ -284,7 +284,7 @@ func TestCommitStoreReaders(t *testing.T) { } gasPrice := big.NewInt(10) daPrice := big.NewInt(20) - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, assets.NewWei(maxGasPrice)).Return(gas.EvmFee{Legacy: assets.NewWei(gasPrice)}, uint64(0), nil) + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, assets.NewWei(maxGasPrice), (*common.Address)(nil)).Return(gas.EvmFee{Legacy: assets.NewWei(gasPrice)}, uint64(0), nil) lm.On("GasPrice", mock.Anything).Return(assets.NewWei(daPrice), nil) for v, cr := range crs { diff --git a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go index 56e1ddb583..031dc25ed8 100644 --- a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go +++ b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go @@ -26,7 +26,7 @@ func NewExecGasPriceEstimator(estimator gas.EvmFeeEstimator, maxGasPrice *big.In } func (g ExecGasPriceEstimator) GetGasPrice(ctx context.Context) (*big.Int, error) { - gasPriceWei, _, err := g.estimator.GetFee(ctx, nil, 0, assets.NewWei(g.maxGasPrice)) + gasPriceWei, _, err := g.estimator.GetFee(ctx, nil, 0, assets.NewWei(g.maxGasPrice), nil) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator_test.go b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator_test.go index e1c2fa0398..6953805709 100644 --- a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator_test.go +++ b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -85,7 +86,7 @@ func TestExecPriceEstimator_GetGasPrice(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { sourceFeeEstimator := mocks.NewEvmFeeEstimator(t) - sourceFeeEstimator.On("GetFee", ctx, []byte(nil), uint64(0), assets.NewWei(tc.maxGasPrice)).Return( + sourceFeeEstimator.On("GetFee", ctx, []byte(nil), uint64(0), assets.NewWei(tc.maxGasPrice), (*common.Address)(nil)).Return( tc.sourceFeeEstimatorRespFee, uint64(0), tc.sourceFeeEstimatorRespErr) g := ExecGasPriceEstimator{ diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go index f84a48c1ff..cc65203e54 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go @@ -45,7 +45,7 @@ func CheckGasPrice(ctx context.Context, upkeepId *big.Int, offchainConfigBytes [ } lggr.Debugf("successfully decode offchain config for %s, max gas price is %s", upkeepId.String(), offchainConfig.MaxGasPrice.String()) - fee, _, err := ge.GetFee(ctx, []byte{}, feeLimit, assets.NewWei(big.NewInt(maxFeePrice))) + fee, _, err := ge.GetFee(ctx, []byte{}, feeLimit, assets.NewWei(big.NewInt(maxFeePrice)), nil) if err != nil { lggr.Errorw("failed to get fee, gas price check is disabled", "upkeepId", upkeepId.String(), "err", err) return encoding.UpkeepFailureReasonNone diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice_test.go index 9b5640051d..4418dd0f7c 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice_test.go @@ -86,13 +86,13 @@ func TestGasPrice_Check(t *testing.T) { ctx := testutils.Context(t) ge := gasMocks.NewEvmFeeEstimator(t) if test.FailedToGetFee { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( gas.EvmFee{}, feeLimit, errors.New("failed to retrieve gas price"), ) } else if test.CurrentLegacyGasPrice != nil { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( gas.EvmFee{ Legacy: assets.NewWei(test.CurrentLegacyGasPrice), }, @@ -100,7 +100,7 @@ func TestGasPrice_Check(t *testing.T) { nil, ) } else if test.CurrentDynamicGasPrice != nil { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( gas.EvmFee{ DynamicFeeCap: assets.NewWei(test.CurrentDynamicGasPrice), DynamicTipCap: assets.NewWei(big.NewInt(1_000_000_000)), diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index f188ffeced..dd2a0684c6 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -186,7 +186,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, w.maxGasPrice) + fee, _, err := w.ge.GetFee(ctx, nil, 0, w.maxGasPrice, nil) 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 66c85bfc2c..e3906a09bd 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -86,7 +86,7 @@ func TestChainWriter(t *testing.T) { }) t.Run("GetFeeComponents", func(t *testing.T) { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + ge.On("GetFee", mock.Anything, 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)), @@ -112,7 +112,7 @@ func TestChainWriter(t *testing.T) { }) 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{ + ge.On("GetFee", mock.Anything, 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)), @@ -124,7 +124,7 @@ func TestChainWriter(t *testing.T) { }) 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{ + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ Legacy: nil, DynamicFeeCap: nil, DynamicTipCap: nil, @@ -136,7 +136,7 @@ func TestChainWriter(t *testing.T) { 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{ + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ Legacy: nil, DynamicFeeCap: nil, DynamicTipCap: nil, @@ -146,7 +146,7 @@ func TestChainWriter(t *testing.T) { }) 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{ + ge.On("GetFee", mock.Anything, 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)), diff --git a/core/web/evm_transfer_controller.go b/core/web/evm_transfer_controller.go index 88d3dead4c..3e14aaccd3 100644 --- a/core/web/evm_transfer_controller.go +++ b/core/web/evm_transfer_controller.go @@ -54,7 +54,7 @@ func (tc *EVMTransfersController) Create(c *gin.Context) { } if !tr.AllowHigherAmounts { - err = ValidateEthBalanceForTransfer(c, chain, tr.FromAddress, tr.Amount) + err = ValidateEthBalanceForTransfer(c, chain, tr.FromAddress, tr.Amount, tr.DestinationAddress) if err != nil { jsonAPIError(c, http.StatusUnprocessableEntity, errors.Errorf("transaction failed: %v", err)) return @@ -92,7 +92,7 @@ func (tc *EVMTransfersController) Create(c *gin.Context) { } // ValidateEthBalanceForTransfer validates that the current balance can cover the transaction amount -func ValidateEthBalanceForTransfer(c *gin.Context, chain legacyevm.Chain, fromAddr common.Address, amount assets.Eth) error { +func ValidateEthBalanceForTransfer(c *gin.Context, chain legacyevm.Chain, fromAddr common.Address, amount assets.Eth, toAddr common.Address) error { var err error var balance *big.Int @@ -116,7 +116,7 @@ func ValidateEthBalanceForTransfer(c *gin.Context, chain legacyevm.Chain, fromAd gasLimit := chain.Config().EVM().GasEstimator().LimitTransfer() estimator := chain.GasEstimator() - amountWithFees, err := estimator.GetMaxCost(c, amount, nil, gasLimit, chain.Config().EVM().GasEstimator().PriceMaxKey(fromAddr)) + amountWithFees, err := estimator.GetMaxCost(c, amount, nil, gasLimit, chain.Config().EVM().GasEstimator().PriceMaxKey(fromAddr), &toAddr) if err != nil { return err } diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index c5547f8698..668ca74ced 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -316,6 +316,7 @@ LimitDefault = 12 LimitMax = 17 LimitMultiplier = '1.234' LimitTransfer = 100 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 10 BumpThreshold = 6 diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index 8c063d0e9e..108dd401a7 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -303,6 +303,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -403,6 +404,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -497,6 +499,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 diff --git a/docs/CONFIG.md b/docs/CONFIG.md index e18abcf811..83d60debba 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1818,6 +1818,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -1912,6 +1913,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2006,6 +2008,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2100,6 +2103,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2195,6 +2199,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -2289,6 +2294,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2383,6 +2389,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2478,6 +2485,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2572,6 +2580,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -2665,6 +2674,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2851,6 +2861,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2945,6 +2956,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -3040,6 +3052,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3134,6 +3147,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -3228,6 +3242,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -3322,6 +3337,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -3416,6 +3432,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -3510,6 +3527,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3604,6 +3622,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -3698,6 +3717,7 @@ LimitDefault = 100000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3792,6 +3812,7 @@ LimitDefault = 2500000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3886,6 +3907,7 @@ LimitDefault = 2500000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3981,6 +4003,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -4075,6 +4098,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4356,6 +4380,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -4450,6 +4475,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4544,6 +4570,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -4638,6 +4665,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4732,6 +4760,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4825,6 +4854,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 0 @@ -4919,6 +4949,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -5013,6 +5044,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -5107,6 +5139,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -5201,6 +5234,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5389,6 +5423,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5483,6 +5518,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -5577,6 +5613,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5672,6 +5709,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5767,6 +5805,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5957,6 +5996,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -6051,6 +6091,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '2 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6145,6 +6186,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6239,6 +6281,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6333,6 +6376,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '2 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6426,6 +6470,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 @@ -6519,6 +6564,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6612,6 +6658,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 @@ -6706,6 +6753,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6894,6 +6942,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -6987,6 +7036,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7176,6 +7226,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -7271,6 +7322,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -7366,6 +7418,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7461,6 +7514,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7556,6 +7610,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7650,6 +7705,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '1 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7744,6 +7800,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '1 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7838,6 +7895,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7932,6 +7990,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -8121,6 +8180,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -8215,6 +8275,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -8577,6 +8638,7 @@ LimitDefault = 8_000_000 # Default LimitMax = 8_000_000 # Default LimitMultiplier = '1.0' # Default LimitTransfer = 21_000 # Default +EstimateGasLimit = false # Default BumpMin = '5 gwei' # Default BumpPercent = 20 # Default BumpThreshold = 3 # Default @@ -8671,6 +8733,12 @@ LimitTransfer = 21_000 # Default ``` LimitTransfer is the gas limit used for an ordinary ETH transfer. +### EstimateGasLimit +```toml +EstimateGasLimit = false # Default +``` +EstimateGasLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound. + ### BumpMin ```toml BumpMin = '5 gwei' # Default diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index fe522ca1bb..135e062cf7 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -359,6 +359,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar index 5a5ce42d6b..9fa53d50b6 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -359,6 +359,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index 12773dc99c..ad38875f18 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -359,6 +359,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index e709b24d5b..6baa4ce92c 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -349,6 +349,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index 9b22da4850..985cf03970 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -356,6 +356,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 +EstimateGasLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 From 3290ed3f34c3fe5310f500b08435dcc6bcac9c36 Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:53:17 -0500 Subject: [PATCH 06/31] Set From address for gas limit estimation feature (#14246) * Set from address for gas limit estimation * Updated tests * Added changeset --- .changeset/great-timers-agree.md | 5 ++ .../chains/evm/gas/mocks/evm_fee_estimator.go | 70 ++++++++++--------- core/chains/evm/gas/models.go | 17 +++-- core/chains/evm/gas/models_test.go | 41 +++++------ core/chains/evm/txmgr/attempts.go | 2 +- core/chains/evm/txmgr/attempts_test.go | 2 +- core/chains/evm/txmgr/broadcaster_test.go | 2 +- core/chains/evm/txmgr/confirmer_test.go | 2 +- core/chains/evm/txmgr/stuck_tx_detector.go | 4 +- .../evm/txmgr/stuck_tx_detector_test.go | 4 +- core/internal/features/features_test.go | 4 +- core/services/keeper/upkeep_executer_test.go | 2 +- .../ccipdata/commit_store_reader_test.go | 2 +- .../ccip/prices/exec_price_estimator.go | 2 +- .../ccip/prices/exec_price_estimator_test.go | 2 +- .../evmregistry/v21/gasprice/gasprice.go | 2 +- .../evmregistry/v21/gasprice/gasprice_test.go | 6 +- core/services/relay/evm/chain_writer.go | 2 +- core/services/relay/evm/chain_writer_test.go | 10 +-- core/web/evm_transfer_controller.go | 2 +- 20 files changed, 95 insertions(+), 88 deletions(-) create mode 100644 .changeset/great-timers-agree.md diff --git a/.changeset/great-timers-agree.md b/.changeset/great-timers-agree.md new file mode 100644 index 0000000000..bfa27761fb --- /dev/null +++ b/.changeset/great-timers-agree.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Updated gas limit estimation feature to set From address #internal diff --git a/core/chains/evm/gas/mocks/evm_fee_estimator.go b/core/chains/evm/gas/mocks/evm_fee_estimator.go index 603115a94c..a7deca2c63 100644 --- a/core/chains/evm/gas/mocks/evm_fee_estimator.go +++ b/core/chains/evm/gas/mocks/evm_fee_estimator.go @@ -147,14 +147,14 @@ func (_c *EvmFeeEstimator_Close_Call) RunAndReturn(run func() error) *EvmFeeEsti return _c } -// GetFee provides a mock function with given fields: ctx, calldata, feeLimit, maxFeePrice, toAddress, opts -func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...types.Opt) (gas.EvmFee, uint64, error) { +// GetFee provides a mock function with given fields: ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts +func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress *common.Address, toAddress *common.Address, opts ...types.Opt) (gas.EvmFee, uint64, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] } var _ca []interface{} - _ca = append(_ca, ctx, calldata, feeLimit, maxFeePrice, toAddress) + _ca = append(_ca, ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress) _ca = append(_ca, _va...) ret := _m.Called(_ca...) @@ -165,23 +165,23 @@ func (_m *EvmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit var r0 gas.EvmFee var r1 uint64 var r2 error - if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) (gas.EvmFee, uint64, error)); ok { - return rf(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) + if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) (gas.EvmFee, uint64, error)); ok { + return rf(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) gas.EvmFee); ok { - r0 = rf(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) + if rf, ok := ret.Get(0).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) gas.EvmFee); ok { + r0 = rf(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...) } else { r0 = ret.Get(0).(gas.EvmFee) } - if rf, ok := ret.Get(1).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) uint64); ok { - r1 = rf(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) + if rf, ok := ret.Get(1).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) uint64); ok { + r1 = rf(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...) } else { r1 = ret.Get(1).(uint64) } - if rf, ok := ret.Get(2).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) error); ok { - r2 = rf(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) + if rf, ok := ret.Get(2).(func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) error); ok { + r2 = rf(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...) } else { r2 = ret.Error(2) } @@ -199,22 +199,23 @@ type EvmFeeEstimator_GetFee_Call struct { // - calldata []byte // - feeLimit uint64 // - maxFeePrice *assets.Wei +// - fromAddress *common.Address // - toAddress *common.Address // - opts ...types.Opt -func (_e *EvmFeeEstimator_Expecter) GetFee(ctx interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, toAddress interface{}, opts ...interface{}) *EvmFeeEstimator_GetFee_Call { +func (_e *EvmFeeEstimator_Expecter) GetFee(ctx interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, fromAddress interface{}, toAddress interface{}, opts ...interface{}) *EvmFeeEstimator_GetFee_Call { return &EvmFeeEstimator_GetFee_Call{Call: _e.mock.On("GetFee", - append([]interface{}{ctx, calldata, feeLimit, maxFeePrice, toAddress}, opts...)...)} + append([]interface{}{ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress}, opts...)...)} } -func (_c *EvmFeeEstimator_GetFee_Call) Run(run func(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...types.Opt)) *EvmFeeEstimator_GetFee_Call { +func (_c *EvmFeeEstimator_GetFee_Call) Run(run func(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress *common.Address, toAddress *common.Address, opts ...types.Opt)) *EvmFeeEstimator_GetFee_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]types.Opt, len(args)-5) - for i, a := range args[5:] { + variadicArgs := make([]types.Opt, len(args)-6) + for i, a := range args[6:] { if a != nil { variadicArgs[i] = a.(types.Opt) } } - run(args[0].(context.Context), args[1].([]byte), args[2].(uint64), args[3].(*assets.Wei), args[4].(*common.Address), variadicArgs...) + run(args[0].(context.Context), args[1].([]byte), args[2].(uint64), args[3].(*assets.Wei), args[4].(*common.Address), args[5].(*common.Address), variadicArgs...) }) return _c } @@ -224,19 +225,19 @@ func (_c *EvmFeeEstimator_GetFee_Call) Return(fee gas.EvmFee, estimatedFeeLimit return _c } -func (_c *EvmFeeEstimator_GetFee_Call) RunAndReturn(run func(context.Context, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) (gas.EvmFee, uint64, error)) *EvmFeeEstimator_GetFee_Call { +func (_c *EvmFeeEstimator_GetFee_Call) RunAndReturn(run func(context.Context, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) (gas.EvmFee, uint64, error)) *EvmFeeEstimator_GetFee_Call { _c.Call.Return(run) return _c } -// GetMaxCost provides a mock function with given fields: ctx, amount, calldata, feeLimit, maxFeePrice, toAddress, opts -func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...types.Opt) (*big.Int, error) { +// GetMaxCost provides a mock function with given fields: ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts +func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress *common.Address, toAddress *common.Address, opts ...types.Opt) (*big.Int, error) { _va := make([]interface{}, len(opts)) for _i := range opts { _va[_i] = opts[_i] } var _ca []interface{} - _ca = append(_ca, ctx, amount, calldata, feeLimit, maxFeePrice, toAddress) + _ca = append(_ca, ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress) _ca = append(_ca, _va...) ret := _m.Called(_ca...) @@ -246,19 +247,19 @@ func (_m *EvmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, ca var r0 *big.Int var r1 error - if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) (*big.Int, error)); ok { - return rf(ctx, amount, calldata, feeLimit, maxFeePrice, toAddress, opts...) + if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) (*big.Int, error)); ok { + return rf(ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...) } - if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) *big.Int); ok { - r0 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, toAddress, opts...) + if rf, ok := ret.Get(0).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) *big.Int); ok { + r0 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*big.Int) } } - if rf, ok := ret.Get(1).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) error); ok { - r1 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, toAddress, opts...) + if rf, ok := ret.Get(1).(func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) error); ok { + r1 = rf(ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...) } else { r1 = ret.Error(1) } @@ -277,22 +278,23 @@ type EvmFeeEstimator_GetMaxCost_Call struct { // - calldata []byte // - feeLimit uint64 // - maxFeePrice *assets.Wei +// - fromAddress *common.Address // - toAddress *common.Address // - opts ...types.Opt -func (_e *EvmFeeEstimator_Expecter) GetMaxCost(ctx interface{}, amount interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, toAddress interface{}, opts ...interface{}) *EvmFeeEstimator_GetMaxCost_Call { +func (_e *EvmFeeEstimator_Expecter) GetMaxCost(ctx interface{}, amount interface{}, calldata interface{}, feeLimit interface{}, maxFeePrice interface{}, fromAddress interface{}, toAddress interface{}, opts ...interface{}) *EvmFeeEstimator_GetMaxCost_Call { return &EvmFeeEstimator_GetMaxCost_Call{Call: _e.mock.On("GetMaxCost", - append([]interface{}{ctx, amount, calldata, feeLimit, maxFeePrice, toAddress}, opts...)...)} + append([]interface{}{ctx, amount, calldata, feeLimit, maxFeePrice, fromAddress, toAddress}, opts...)...)} } -func (_c *EvmFeeEstimator_GetMaxCost_Call) Run(run func(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...types.Opt)) *EvmFeeEstimator_GetMaxCost_Call { +func (_c *EvmFeeEstimator_GetMaxCost_Call) Run(run func(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress *common.Address, toAddress *common.Address, opts ...types.Opt)) *EvmFeeEstimator_GetMaxCost_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]types.Opt, len(args)-6) - for i, a := range args[6:] { + variadicArgs := make([]types.Opt, len(args)-7) + for i, a := range args[7:] { if a != nil { variadicArgs[i] = a.(types.Opt) } } - run(args[0].(context.Context), args[1].(assets.Eth), args[2].([]byte), args[3].(uint64), args[4].(*assets.Wei), args[5].(*common.Address), variadicArgs...) + run(args[0].(context.Context), args[1].(assets.Eth), args[2].([]byte), args[3].(uint64), args[4].(*assets.Wei), args[5].(*common.Address), args[6].(*common.Address), variadicArgs...) }) return _c } @@ -302,7 +304,7 @@ func (_c *EvmFeeEstimator_GetMaxCost_Call) Return(_a0 *big.Int, _a1 error) *EvmF return _c } -func (_c *EvmFeeEstimator_GetMaxCost_Call) RunAndReturn(run func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, ...types.Opt) (*big.Int, error)) *EvmFeeEstimator_GetMaxCost_Call { +func (_c *EvmFeeEstimator_GetMaxCost_Call) RunAndReturn(run func(context.Context, assets.Eth, []byte, uint64, *assets.Wei, *common.Address, *common.Address, ...types.Opt) (*big.Int, error)) *EvmFeeEstimator_GetMaxCost_Call { _c.Call.Return(run) return _c } diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index 0da56a84d8..36e42771af 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -36,11 +36,11 @@ type EvmFeeEstimator interface { // L1Oracle returns the L1 gas price oracle only if the chain has one, e.g. OP stack L2s and Arbitrum. L1Oracle() rollups.L1Oracle - GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) + GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) BumpFee(ctx context.Context, originalFee EvmFee, feeLimit uint64, maxFeePrice *assets.Wei, attempts []EvmPriorAttempt) (bumpedFee EvmFee, chainSpecificFeeLimit uint64, err error) // GetMaxCost returns the total value = max price x fee units + transferred value - GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (*big.Int, error) + GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (*big.Int, error) } type feeEstimatorClient interface { @@ -271,7 +271,7 @@ func (e *evmFeeEstimator) L1Oracle() rollups.L1Oracle { // GetFee returns an initial estimated gas price and gas limit for a transaction // The gas limit provided by the caller can be adjusted by gas estimation or for 2D fees -func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) { +func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, estimatedFeeLimit uint64, err error) { var chainSpecificFeeLimit uint64 // get dynamic fee if e.EIP1559Enabled { @@ -291,12 +291,12 @@ func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit } } - estimatedFeeLimit, err = e.estimateFeeLimit(ctx, chainSpecificFeeLimit, calldata, toAddress) + estimatedFeeLimit, err = e.estimateFeeLimit(ctx, chainSpecificFeeLimit, calldata, fromAddress, toAddress) return } -func (e *evmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (*big.Int, error) { - fees, gasLimit, err := e.GetFee(ctx, calldata, feeLimit, maxFeePrice, toAddress, opts...) +func (e *evmFeeEstimator) GetMaxCost(ctx context.Context, amount assets.Eth, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (*big.Int, error) { + fees, gasLimit, err := e.GetFee(ctx, calldata, feeLimit, maxFeePrice, fromAddress, toAddress, opts...) if err != nil { return nil, err } @@ -347,7 +347,7 @@ func (e *evmFeeEstimator) BumpFee(ctx context.Context, originalFee EvmFee, feeLi return } -func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, feeLimit uint64, calldata []byte, toAddress *common.Address) (estimatedFeeLimit uint64, err error) { +func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, feeLimit uint64, calldata []byte, fromAddress, toAddress *common.Address) (estimatedFeeLimit uint64, err error) { // Use the feeLimit * LimitMultiplier as the provided gas limit since this multiplier is applied on top of the caller specified gas limit providedGasLimit, err := commonfee.ApplyMultiplier(feeLimit, e.geCfg.LimitMultiplier()) if err != nil { @@ -363,6 +363,9 @@ func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, feeLimit uint64, To: toAddress, Data: calldata, } + if fromAddress != nil { + callMsg.From = *fromAddress + } estimatedGas, estimateErr := e.ethClient.EstimateGas(ctx, callMsg) if estimateErr != nil { if providedGasLimit > 0 { diff --git a/core/chains/evm/gas/models_test.go b/core/chains/evm/gas/models_test.go index 14ef085497..ea5e53c228 100644 --- a/core/chains/evm/gas/models_test.go +++ b/core/chains/evm/gas/models_test.go @@ -52,6 +52,9 @@ func TestWrappedEvmEstimator(t *testing.T) { mockEstimatorName := "WrappedEvmEstimator" mockEvmEstimatorName := "WrappedEvmEstimator.MockEstimator" + fromAddress := testutils.NewAddress() + toAddress := testutils.NewAddress() + // L1Oracle returns the correct L1Oracle interface t.Run("L1Oracle", func(t *testing.T) { lggr := logger.Test(t) @@ -84,7 +87,7 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect legacy fee data dynamicFees := false estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) - fee, max, err := estimator.GetFee(ctx, nil, 0, nil, nil) + fee, max, err := estimator.GetFee(ctx, nil, 0, nil, nil, nil) require.NoError(t, err) assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), max) assert.True(t, legacyFee.Equal(fee.Legacy)) @@ -94,7 +97,7 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect dynamic fee data dynamicFees = true estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) - fee, max, err = estimator.GetFee(ctx, nil, gasLimit, nil, nil) + fee, max, err = estimator.GetFee(ctx, nil, gasLimit, nil, nil, nil) require.NoError(t, err) assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), max) assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) @@ -145,7 +148,7 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect legacy fee data dynamicFees := false estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) - total, err := estimator.GetMaxCost(ctx, val, nil, gasLimit, nil, nil) + total, err := estimator.GetMaxCost(ctx, val, nil, gasLimit, nil, nil, nil) require.NoError(t, err) fee := new(big.Int).Mul(legacyFee.ToInt(), big.NewInt(int64(gasLimit))) fee, _ = new(big.Float).Mul(new(big.Float).SetInt(fee), big.NewFloat(float64(limitMultiplier))).Int(nil) @@ -154,7 +157,7 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect dynamic fee data dynamicFees = true estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, nil) - total, err = estimator.GetMaxCost(ctx, val, nil, gasLimit, nil, nil) + total, err = estimator.GetMaxCost(ctx, val, nil, gasLimit, nil, nil, nil) require.NoError(t, err) fee = new(big.Int).Mul(dynamicFee.FeeCap.ToInt(), big.NewInt(int64(gasLimit))) fee, _ = new(big.Float).Mul(new(big.Float).SetInt(fee), big.NewFloat(float64(limitMultiplier))).Int(nil) @@ -262,8 +265,7 @@ func TestWrappedEvmEstimator(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - toAddress := testutils.NewAddress() - fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress) require.NoError(t, err) assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit) assert.True(t, legacyFee.Equal(fee.Legacy)) @@ -273,7 +275,7 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect dynamic fee data dynamicFees = true estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress) require.NoError(t, err) assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit) assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) @@ -290,14 +292,13 @@ func TestWrappedEvmEstimator(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - toAddress := testutils.NewAddress() - _, _, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + _, _, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress) require.ErrorIs(t, err, commonfee.ErrFeeLimitTooLow) // expect dynamic fee data dynamicFees = true estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - _, _, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + _, _, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress) require.ErrorIs(t, err, commonfee.ErrFeeLimitTooLow) }) @@ -309,8 +310,7 @@ func TestWrappedEvmEstimator(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - toAddress := testutils.NewAddress() - fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress) require.NoError(t, err) assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) assert.True(t, legacyFee.Equal(fee.Legacy)) @@ -319,7 +319,7 @@ func TestWrappedEvmEstimator(t *testing.T) { dynamicFees = true // expect dynamic fee data estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress) require.NoError(t, err) assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) @@ -335,8 +335,7 @@ func TestWrappedEvmEstimator(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(0), errors.New("something broke")).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - toAddress := testutils.NewAddress() - fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + fee, limit, err := estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress) require.NoError(t, err) assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) assert.True(t, legacyFee.Equal(fee.Legacy)) @@ -346,7 +345,7 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect dynamic fee data dynamicFees = true estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &toAddress) + fee, limit, err = estimator.GetFee(ctx, []byte{}, gasLimit, nil, &fromAddress, &toAddress) require.NoError(t, err) assert.Equal(t, uint64(float32(gasLimit)*limitMultiplier), limit) assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) @@ -367,8 +366,7 @@ func TestWrappedEvmEstimator(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - toAddress := testutils.NewAddress() - fee, limit, err := estimator.GetFee(ctx, []byte{}, uint64(0), nil, &toAddress) + fee, limit, err := estimator.GetFee(ctx, []byte{}, uint64(0), nil, &fromAddress, &toAddress) require.NoError(t, err) assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit) assert.True(t, legacyFee.Equal(fee.Legacy)) @@ -378,7 +376,7 @@ func TestWrappedEvmEstimator(t *testing.T) { // expect dynamic fee data dynamicFees = true estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - fee, limit, err = estimator.GetFee(ctx, []byte{}, 0, nil, &toAddress) + fee, limit, err = estimator.GetFee(ctx, []byte{}, 0, nil, &fromAddress, &toAddress) require.NoError(t, err) assert.Equal(t, uint64(float32(estimatedGasLimit)*gas.EstimateGasBuffer), limit) assert.True(t, dynamicFee.FeeCap.Equal(fee.DynamicFeeCap)) @@ -398,14 +396,13 @@ func TestWrappedEvmEstimator(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(0), errors.New("something broke")).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - toAddress := testutils.NewAddress() - _, _, err := estimator.GetFee(ctx, []byte{}, 0, nil, &toAddress) + _, _, err := estimator.GetFee(ctx, []byte{}, 0, nil, &fromAddress, &toAddress) require.Error(t, err) // expect dynamic fee data dynamicFees = true estimator = gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) - _, _, err = estimator.GetFee(ctx, []byte{}, 0, nil, &toAddress) + _, _, err = estimator.GetFee(ctx, []byte{}, 0, nil, &fromAddress, &toAddress) require.Error(t, err) }) } diff --git a/core/chains/evm/txmgr/attempts.go b/core/chains/evm/txmgr/attempts.go index c57ecc4412..c284ee77bd 100644 --- a/core/chains/evm/txmgr/attempts.go +++ b/core/chains/evm/txmgr/attempts.go @@ -58,7 +58,7 @@ func (c *evmTxAttemptBuilder) NewTxAttempt(ctx context.Context, etx Tx, lggr log // used for L2 re-estimation on broadcasting (note EIP1559 must be disabled otherwise this will fail with mismatched fees + tx type) func (c *evmTxAttemptBuilder) NewTxAttemptWithType(ctx context.Context, etx Tx, lggr logger.Logger, txType int, opts ...feetypes.Opt) (attempt TxAttempt, fee gas.EvmFee, feeLimit uint64, retryable bool, err error) { keySpecificMaxGasPriceWei := c.feeConfig.PriceMaxKey(etx.FromAddress) - fee, feeLimit, err = c.EvmFeeEstimator.GetFee(ctx, etx.EncodedPayload, etx.FeeLimit, keySpecificMaxGasPriceWei, &etx.ToAddress, opts...) + fee, feeLimit, err = c.EvmFeeEstimator.GetFee(ctx, etx.EncodedPayload, etx.FeeLimit, keySpecificMaxGasPriceWei, &etx.FromAddress, &etx.ToAddress, opts...) if err != nil { return attempt, fee, feeLimit, true, pkgerrors.Wrap(err, "failed to get fee") // estimator errors are retryable } diff --git a/core/chains/evm/txmgr/attempts_test.go b/core/chains/evm/txmgr/attempts_test.go index ea00f7a347..5c43368fcc 100644 --- a/core/chains/evm/txmgr/attempts_test.go +++ b/core/chains/evm/txmgr/attempts_test.go @@ -339,7 +339,7 @@ func TestTxm_NewCustomTxAttempt_NonRetryableErrors(t *testing.T) { func TestTxm_EvmTxAttemptBuilder_RetryableEstimatorError(t *testing.T) { est := gasmocks.NewEvmFeeEstimator(t) - est.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail")) + est.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail")) est.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{}, uint64(0), pkgerrors.New("fail")) kst := ksmocks.NewEth(t) diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index 343988196c..41f50f4434 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -644,7 +644,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi chStartEstimate := make(chan struct{}) chBlock := make(chan struct{}) - estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, ccfg.EVM().GasEstimator().PriceMaxKey(fromAddress), mock.Anything).Return(gas.EvmFee{Legacy: assets.GWei(32)}, uint64(500), nil).Run(func(_ mock.Arguments) { + estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, ccfg.EVM().GasEstimator().PriceMaxKey(fromAddress), mock.Anything, mock.Anything).Return(gas.EvmFee{Legacy: assets.GWei(32)}, uint64(500), nil).Run(func(_ mock.Arguments) { close(chStartEstimate) <-chBlock }).Once() diff --git a/core/chains/evm/txmgr/confirmer_test.go b/core/chains/evm/txmgr/confirmer_test.go index daba2e8d92..82b668f168 100644 --- a/core/chains/evm/txmgr/confirmer_test.go +++ b/core/chains/evm/txmgr/confirmer_test.go @@ -3195,7 +3195,7 @@ func TestEthConfirmer_ProcessStuckTransactions(t *testing.T) { fee := gas.EvmFee{Legacy: marketGasPrice} bumpedLegacy := assets.GWei(30) bumpedFee := gas.EvmFee{Legacy: bumpedLegacy} - feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything).Return(fee, uint64(0), nil) + feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything, mock.Anything).Return(fee, uint64(0), nil) feeEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(bumpedFee, uint64(10_000), nil) autoPurgeThreshold := uint32(5) autoPurgeMinAttempts := uint32(3) diff --git a/core/chains/evm/txmgr/stuck_tx_detector.go b/core/chains/evm/txmgr/stuck_tx_detector.go index 4e521d5f8f..362bb6c0a5 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector.go +++ b/core/chains/evm/txmgr/stuck_tx_detector.go @@ -25,7 +25,7 @@ import ( ) type stuckTxDetectorGasEstimator interface { - GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee gas.EvmFee, chainSpecificFeeLimit uint64, err error) + GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, fromAddress, toAddress *common.Address, opts ...feetypes.Opt) (fee gas.EvmFee, chainSpecificFeeLimit uint64, err error) } type stuckTxDetectorClient interface { @@ -199,7 +199,7 @@ func (d *stuckTxDetector) detectStuckTransactionsHeuristic(ctx context.Context, defer d.purgeBlockNumLock.RUnlock() // Get gas price from internal gas estimator // Send with max gas price time 2 to prevent the results from being capped. Need the market gas price here. - marketGasPrice, _, err := d.gasEstimator.GetFee(ctx, []byte{}, 0, d.maxPrice.Mul(big.NewInt(2)), nil) + marketGasPrice, _, err := d.gasEstimator.GetFee(ctx, []byte{}, 0, d.maxPrice.Mul(big.NewInt(2)), nil, nil) if err != nil { return txs, fmt.Errorf("failed to get market gas price for overflow detection: %w", err) } diff --git a/core/chains/evm/txmgr/stuck_tx_detector_test.go b/core/chains/evm/txmgr/stuck_tx_detector_test.go index 5e022091a6..eb22830ef3 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector_test.go +++ b/core/chains/evm/txmgr/stuck_tx_detector_test.go @@ -73,7 +73,7 @@ func TestStuckTxDetector_LoadPurgeBlockNumMap(t *testing.T) { feeEstimator := gasmocks.NewEvmFeeEstimator(t) marketGasPrice := assets.GWei(15) fee := gas.EvmFee{Legacy: marketGasPrice} - feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything).Return(fee, uint64(0), nil) + feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything, mock.Anything).Return(fee, uint64(0), nil) autoPurgeThreshold := uint32(5) autoPurgeMinAttempts := uint32(3) autoPurgeCfg := testAutoPurgeConfig{ @@ -194,7 +194,7 @@ func TestStuckTxDetector_DetectStuckTransactionsHeuristic(t *testing.T) { // Return 10 gwei as market gas price marketGasPrice := tenGwei fee := gas.EvmFee{Legacy: marketGasPrice} - feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything).Return(fee, uint64(0), nil) + feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything, mock.Anything).Return(fee, uint64(0), nil) ethClient := testutils.NewEthClientMockWithDefaultChain(t) autoPurgeThreshold := uint32(5) autoPurgeMinAttempts := uint32(3) diff --git a/core/internal/features/features_test.go b/core/internal/features/features_test.go index 3ce14ef284..7be0307798 100644 --- a/core/internal/features/features_test.go +++ b/core/internal/features/features_test.go @@ -1339,7 +1339,7 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) { chain := evmtest.MustGetDefaultChain(t, legacyChains) estimator := chain.GasEstimator() - gasPrice, gasLimit, err := estimator.GetFee(testutils.Context(t), nil, 500_000, maxGasPrice, nil) + gasPrice, gasLimit, err := estimator.GetFee(testutils.Context(t), nil, 500_000, maxGasPrice, nil, nil) require.NoError(t, err) assert.Equal(t, uint64(500000), gasLimit) assert.Equal(t, "41.5 gwei", gasPrice.Legacy.String()) @@ -1360,7 +1360,7 @@ func TestIntegration_BlockHistoryEstimator(t *testing.T) { newHeads.TrySend(h43) gomega.NewWithT(t).Eventually(func() string { - gasPrice, _, err := estimator.GetFee(testutils.Context(t), nil, 500000, maxGasPrice, nil) + gasPrice, _, err := estimator.GetFee(testutils.Context(t), nil, 500000, maxGasPrice, nil, nil) require.NoError(t, err) return gasPrice.Legacy.String() }, testutils.WaitTimeout(t), cltest.DBPollingInterval).Should(gomega.Equal("45 gwei")) diff --git a/core/services/keeper/upkeep_executer_test.go b/core/services/keeper/upkeep_executer_test.go index 0f68543904..55926242a2 100644 --- a/core/services/keeper/upkeep_executer_test.go +++ b/core/services/keeper/upkeep_executer_test.go @@ -48,7 +48,7 @@ func mockEstimator(t *testing.T) gas.EvmFeeEstimator { // note: estimator will only return 1 of legacy or dynamic fees (not both) // assumed to call legacy estimator only estimator := gasmocks.NewEvmFeeEstimator(t) - estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(gas.EvmFee{ + estimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Maybe().Return(gas.EvmFee{ Legacy: assets.GWei(60), }, uint32(60), nil) return estimator diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go index ad85b6e682..4c43edcc05 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go @@ -284,7 +284,7 @@ func TestCommitStoreReaders(t *testing.T) { } gasPrice := big.NewInt(10) daPrice := big.NewInt(20) - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, assets.NewWei(maxGasPrice), (*common.Address)(nil)).Return(gas.EvmFee{Legacy: assets.NewWei(gasPrice)}, uint64(0), nil) + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, assets.NewWei(maxGasPrice), (*common.Address)(nil), (*common.Address)(nil)).Return(gas.EvmFee{Legacy: assets.NewWei(gasPrice)}, uint64(0), nil) lm.On("GasPrice", mock.Anything).Return(assets.NewWei(daPrice), nil) for v, cr := range crs { diff --git a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go index 031dc25ed8..84a6014bef 100644 --- a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go +++ b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go @@ -26,7 +26,7 @@ func NewExecGasPriceEstimator(estimator gas.EvmFeeEstimator, maxGasPrice *big.In } func (g ExecGasPriceEstimator) GetGasPrice(ctx context.Context) (*big.Int, error) { - gasPriceWei, _, err := g.estimator.GetFee(ctx, nil, 0, assets.NewWei(g.maxGasPrice), nil) + gasPriceWei, _, err := g.estimator.GetFee(ctx, nil, 0, assets.NewWei(g.maxGasPrice), nil, nil) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator_test.go b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator_test.go index 6953805709..f9ba1523e5 100644 --- a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator_test.go +++ b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator_test.go @@ -86,7 +86,7 @@ func TestExecPriceEstimator_GetGasPrice(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { sourceFeeEstimator := mocks.NewEvmFeeEstimator(t) - sourceFeeEstimator.On("GetFee", ctx, []byte(nil), uint64(0), assets.NewWei(tc.maxGasPrice), (*common.Address)(nil)).Return( + sourceFeeEstimator.On("GetFee", ctx, []byte(nil), uint64(0), assets.NewWei(tc.maxGasPrice), (*common.Address)(nil), (*common.Address)(nil)).Return( tc.sourceFeeEstimatorRespFee, uint64(0), tc.sourceFeeEstimatorRespErr) g := ExecGasPriceEstimator{ diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go index cc65203e54..36460683d4 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice.go @@ -45,7 +45,7 @@ func CheckGasPrice(ctx context.Context, upkeepId *big.Int, offchainConfigBytes [ } lggr.Debugf("successfully decode offchain config for %s, max gas price is %s", upkeepId.String(), offchainConfig.MaxGasPrice.String()) - fee, _, err := ge.GetFee(ctx, []byte{}, feeLimit, assets.NewWei(big.NewInt(maxFeePrice)), nil) + fee, _, err := ge.GetFee(ctx, []byte{}, feeLimit, assets.NewWei(big.NewInt(maxFeePrice)), nil, nil) if err != nil { lggr.Errorw("failed to get fee, gas price check is disabled", "upkeepId", upkeepId.String(), "err", err) return encoding.UpkeepFailureReasonNone diff --git a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice_test.go b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice_test.go index 4418dd0f7c..7b5ef999f3 100644 --- a/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice_test.go +++ b/core/services/ocr2/plugins/ocr2keeper/evmregistry/v21/gasprice/gasprice_test.go @@ -86,13 +86,13 @@ func TestGasPrice_Check(t *testing.T) { ctx := testutils.Context(t) ge := gasMocks.NewEvmFeeEstimator(t) if test.FailedToGetFee { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( gas.EvmFee{}, feeLimit, errors.New("failed to retrieve gas price"), ) } else if test.CurrentLegacyGasPrice != nil { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( gas.EvmFee{ Legacy: assets.NewWei(test.CurrentLegacyGasPrice), }, @@ -100,7 +100,7 @@ func TestGasPrice_Check(t *testing.T) { nil, ) } else if test.CurrentDynamicGasPrice != nil { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return( gas.EvmFee{ DynamicFeeCap: assets.NewWei(test.CurrentDynamicGasPrice), DynamicTipCap: assets.NewWei(big.NewInt(1_000_000_000)), diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index dd2a0684c6..466811d115 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -186,7 +186,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, w.maxGasPrice, nil) + fee, _, err := w.ge.GetFee(ctx, nil, 0, w.maxGasPrice, nil, nil) 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 e3906a09bd..e3fc8f8e22 100644 --- a/core/services/relay/evm/chain_writer_test.go +++ b/core/services/relay/evm/chain_writer_test.go @@ -86,7 +86,7 @@ func TestChainWriter(t *testing.T) { }) t.Run("GetFeeComponents", func(t *testing.T) { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + ge.On("GetFee", mock.Anything, mock.Anything, 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)), @@ -112,7 +112,7 @@ func TestChainWriter(t *testing.T) { }) 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, mock.Anything).Return(gas.EvmFee{ + ge.On("GetFee", mock.Anything, mock.Anything, 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)), @@ -124,7 +124,7 @@ func TestChainWriter(t *testing.T) { }) 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, mock.Anything).Return(gas.EvmFee{ + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ Legacy: nil, DynamicFeeCap: nil, DynamicTipCap: nil, @@ -136,7 +136,7 @@ func TestChainWriter(t *testing.T) { 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, mock.Anything).Return(gas.EvmFee{ + ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ Legacy: nil, DynamicFeeCap: nil, DynamicTipCap: nil, @@ -146,7 +146,7 @@ func TestChainWriter(t *testing.T) { }) t.Run("Fails when L1Oracle returns error", func(t *testing.T) { - ge.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(gas.EvmFee{ + ge.On("GetFee", mock.Anything, mock.Anything, 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)), diff --git a/core/web/evm_transfer_controller.go b/core/web/evm_transfer_controller.go index 3e14aaccd3..75f6c07b6d 100644 --- a/core/web/evm_transfer_controller.go +++ b/core/web/evm_transfer_controller.go @@ -116,7 +116,7 @@ func ValidateEthBalanceForTransfer(c *gin.Context, chain legacyevm.Chain, fromAd gasLimit := chain.Config().EVM().GasEstimator().LimitTransfer() estimator := chain.GasEstimator() - amountWithFees, err := estimator.GetMaxCost(c, amount, nil, gasLimit, chain.Config().EVM().GasEstimator().PriceMaxKey(fromAddr), &toAddr) + amountWithFees, err := estimator.GetMaxCost(c, amount, nil, gasLimit, chain.Config().EVM().GasEstimator().PriceMaxKey(fromAddr), &fromAddr, &toAddr) if err != nil { return err } From ee84efc5136aa0803d755cc6903cdf4164012cd3 Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Wed, 4 Sep 2024 02:33:45 +0900 Subject: [PATCH 07/31] Update EstimateGasLimit config name to EstimateLimit (#14297) * Updated EstimateGasLimit config name to EstimateLimit * Updated mocks * Fixed linting * Updated changeset --- .changeset/loud-windows-call.md | 2 +- .../ocrimpls/contract_transmitter_test.go | 2 +- .../evm/config/chain_scoped_gas_estimator.go | 4 +- core/chains/evm/config/config.go | 2 +- core/chains/evm/config/config_test.go | 1 + core/chains/evm/config/mocks/gas_estimator.go | 22 +-- core/chains/evm/config/toml/config.go | 16 +-- .../evm/config/toml/defaults/fallback.toml | 2 +- core/chains/evm/gas/helpers_test.go | 6 +- core/chains/evm/gas/models.go | 12 +- core/chains/evm/gas/models_test.go | 12 +- core/chains/evm/txmgr/broadcaster_test.go | 2 +- core/chains/evm/txmgr/test_helpers.go | 2 +- core/config/docs/chains-evm.toml | 4 +- core/services/chainlink/config_test.go | 4 +- .../chainlink/testdata/config-full.toml | 2 +- .../config-multi-chain-effective.toml | 6 +- core/web/resolver/testdata/config-full.toml | 2 +- .../config-multi-chain-effective.toml | 6 +- docs/CONFIG.md | 130 +++++++++--------- .../disk-based-logging-disabled.txtar | 2 +- .../validate/disk-based-logging-no-dir.txtar | 2 +- .../node/validate/disk-based-logging.txtar | 2 +- testdata/scripts/node/validate/invalid.txtar | 2 +- testdata/scripts/node/validate/valid.txtar | 2 +- 25 files changed, 125 insertions(+), 124 deletions(-) diff --git a/.changeset/loud-windows-call.md b/.changeset/loud-windows-call.md index e05bed706a..6dc8d6fac7 100644 --- a/.changeset/loud-windows-call.md +++ b/.changeset/loud-windows-call.md @@ -2,4 +2,4 @@ "chainlink": minor --- -Added gas limit estimation feature to EVM gas estimators #added +Added gas limit estimation feature to EVM gas estimators. Introduced a new config `EVM.GasEstimator.EstimateLimit` to toggle this feature. #added diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index bd0bf9a2e3..62da2f188f 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -612,7 +612,7 @@ func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType { func (g *TestGasEstimatorConfig) PriceMaxKey(addr common.Address) *assets.Wei { return assets.GWei(1) } -func (g *TestGasEstimatorConfig) EstimateGasLimit() bool { return false } +func (g *TestGasEstimatorConfig) EstimateLimit() bool { return false } func (e *TestEvmConfig) GasEstimator() evmconfig.GasEstimator { return &TestGasEstimatorConfig{bumpThreshold: e.BumpThreshold} diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index 4f2d8872d8..6f43839b01 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -108,8 +108,8 @@ func (g *gasEstimatorConfig) LimitJobType() LimitJobType { return &limitJobTypeConfig{c: g.c.LimitJobType} } -func (g *gasEstimatorConfig) EstimateGasLimit() bool { - return *g.c.EstimateGasLimit +func (g *gasEstimatorConfig) EstimateLimit() bool { + return *g.c.EstimateLimit } type limitJobTypeConfig struct { diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index 9517c68716..9237cf5e1e 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -136,7 +136,7 @@ type GasEstimator interface { PriceMin() *assets.Wei Mode() string PriceMaxKey(gethcommon.Address) *assets.Wei - EstimateGasLimit() bool + EstimateLimit() bool } type LimitJobType interface { diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go index 617a1605d8..305737a87c 100644 --- a/core/chains/evm/config/config_test.go +++ b/core/chains/evm/config/config_test.go @@ -243,6 +243,7 @@ func TestChainScopedConfig_GasEstimator(t *testing.T) { assert.Equal(t, assets.GWei(100), ge.FeeCapDefault()) assert.Equal(t, assets.NewWeiI(1), ge.TipCapDefault()) assert.Equal(t, assets.NewWeiI(1), ge.TipCapMin()) + assert.Equal(t, false, ge.EstimateLimit()) } func TestChainScopedConfig_BSCDefaults(t *testing.T) { diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go index 40c10757b8..96ffff08ae 100644 --- a/core/chains/evm/config/mocks/gas_estimator.go +++ b/core/chains/evm/config/mocks/gas_estimator.go @@ -298,12 +298,12 @@ func (_c *GasEstimator_EIP1559DynamicFees_Call) RunAndReturn(run func() bool) *G return _c } -// EstimateGasLimit provides a mock function with given fields: -func (_m *GasEstimator) EstimateGasLimit() bool { +// EstimateLimit provides a mock function with given fields: +func (_m *GasEstimator) EstimateLimit() bool { ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for EstimateGasLimit") + panic("no return value specified for EstimateLimit") } var r0 bool @@ -316,29 +316,29 @@ func (_m *GasEstimator) EstimateGasLimit() bool { return r0 } -// GasEstimator_EstimateGasLimit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateGasLimit' -type GasEstimator_EstimateGasLimit_Call struct { +// GasEstimator_EstimateLimit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EstimateLimit' +type GasEstimator_EstimateLimit_Call struct { *mock.Call } -// EstimateGasLimit is a helper method to define mock.On call -func (_e *GasEstimator_Expecter) EstimateGasLimit() *GasEstimator_EstimateGasLimit_Call { - return &GasEstimator_EstimateGasLimit_Call{Call: _e.mock.On("EstimateGasLimit")} +// EstimateLimit is a helper method to define mock.On call +func (_e *GasEstimator_Expecter) EstimateLimit() *GasEstimator_EstimateLimit_Call { + return &GasEstimator_EstimateLimit_Call{Call: _e.mock.On("EstimateLimit")} } -func (_c *GasEstimator_EstimateGasLimit_Call) Run(run func()) *GasEstimator_EstimateGasLimit_Call { +func (_c *GasEstimator_EstimateLimit_Call) Run(run func()) *GasEstimator_EstimateLimit_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *GasEstimator_EstimateGasLimit_Call) Return(_a0 bool) *GasEstimator_EstimateGasLimit_Call { +func (_c *GasEstimator_EstimateLimit_Call) Return(_a0 bool) *GasEstimator_EstimateLimit_Call { _c.Call.Return(_a0) return _c } -func (_c *GasEstimator_EstimateGasLimit_Call) RunAndReturn(run func() bool) *GasEstimator_EstimateGasLimit_Call { +func (_c *GasEstimator_EstimateLimit_Call) RunAndReturn(run func() bool) *GasEstimator_EstimateLimit_Call { _c.Call.Return(run) return _c } diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index 99e61a394b..0e823ffdda 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -549,12 +549,12 @@ type GasEstimator struct { PriceMax *assets.Wei PriceMin *assets.Wei - LimitDefault *uint64 - LimitMax *uint64 - LimitMultiplier *decimal.Decimal - LimitTransfer *uint64 - LimitJobType GasLimitJobType `toml:",omitempty"` - EstimateGasLimit *bool + LimitDefault *uint64 + LimitMax *uint64 + LimitMultiplier *decimal.Decimal + LimitTransfer *uint64 + LimitJobType GasLimitJobType `toml:",omitempty"` + EstimateLimit *bool BumpMin *assets.Wei BumpPercent *uint16 @@ -642,8 +642,8 @@ func (e *GasEstimator) setFrom(f *GasEstimator) { if v := f.LimitTransfer; v != nil { e.LimitTransfer = v } - if v := f.EstimateGasLimit; v != nil { - e.EstimateGasLimit = v + if v := f.EstimateLimit; v != nil { + e.EstimateLimit = v } if v := f.PriceDefault; v != nil { e.PriceDefault = v diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index 71da1df208..be3b19677d 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -48,7 +48,7 @@ EIP1559DynamicFees = false FeeCapDefault = '100 gwei' TipCapDefault = '1' TipCapMin = '1' -EstimateGasLimit = false +EstimateLimit = false [GasEstimator.BlockHistory] BatchSize = 25 diff --git a/core/chains/evm/gas/helpers_test.go b/core/chains/evm/gas/helpers_test.go index d6af645fe8..0c9a74a18d 100644 --- a/core/chains/evm/gas/helpers_test.go +++ b/core/chains/evm/gas/helpers_test.go @@ -157,7 +157,7 @@ type MockGasEstimatorConfig struct { FeeCapDefaultF *assets.Wei LimitMaxF uint64 ModeF string - EstimateGasLimitF bool + EstimateLimitF bool } func NewMockGasConfig() *MockGasEstimatorConfig { @@ -216,6 +216,6 @@ func (m *MockGasEstimatorConfig) Mode() string { return m.ModeF } -func (m *MockGasEstimatorConfig) EstimateGasLimit() bool { - return m.EstimateGasLimitF +func (m *MockGasEstimatorConfig) EstimateLimit() bool { + return m.EstimateLimitF } diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index 36e42771af..c1c2e60320 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -26,7 +26,7 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) -// EstimateGasBuffer is a multiplier applied to estimated gas when the EstimateGasLimit feature is enabled +// EstimateGasBuffer is a multiplier applied to estimated gas when the EstimateLimit feature is enabled const EstimateGasBuffer = float32(1.15) // EvmFeeEstimator provides a unified interface that wraps EvmEstimator and can determine if legacy or dynamic fee estimation should be used @@ -74,7 +74,7 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, "tipCapMin", geCfg.TipCapMin(), "priceMax", geCfg.PriceMax(), "priceMin", geCfg.PriceMin(), - "estimateGasLimit", geCfg.EstimateGasLimit(), + "estimateLimit", geCfg.EstimateLimit(), ) df := geCfg.EIP1559DynamicFees() @@ -353,8 +353,8 @@ func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, feeLimit uint64, if err != nil { return estimatedFeeLimit, err } - // Use provided fee limit by default if EstimateGasLimit is disabled - if !e.geCfg.EstimateGasLimit() { + // Use provided fee limit by default if EstimateLimit is disabled + if !e.geCfg.EstimateLimit() { return providedGasLimit, nil } // Create call msg for gas limit estimation @@ -393,7 +393,7 @@ func (e *evmFeeEstimator) estimateFeeLimit(ctx context.Context, feeLimit uint64, e.lggr.Debugw("estimated gas limit with buffer exceeds the provided gas limit with multiplier. falling back to the provided gas limit with multiplier", "estimatedGasLimit", estimatedFeeLimit, "providedGasLimitWithMultiplier", providedGasLimit) estimatedFeeLimit = providedGasLimit } - + return } @@ -418,7 +418,7 @@ type GasEstimatorConfig interface { PriceMin() *assets.Wei PriceMax() *assets.Wei Mode() string - EstimateGasLimit() bool + EstimateLimit() bool } type BlockHistoryConfig interface { diff --git a/core/chains/evm/gas/models_test.go b/core/chains/evm/gas/models_test.go index ea5e53c228..f2afc26c85 100644 --- a/core/chains/evm/gas/models_test.go +++ b/core/chains/evm/gas/models_test.go @@ -261,7 +261,7 @@ func TestWrappedEvmEstimator(t *testing.T) { lggr := logger.Test(t) // expect legacy fee data dynamicFees := false - geCfg.EstimateGasLimitF = true + geCfg.EstimateLimitF = true ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) @@ -288,7 +288,7 @@ func TestWrappedEvmEstimator(t *testing.T) { lggr := logger.Test(t) // expect legacy fee data dynamicFees := false - geCfg.EstimateGasLimitF = true + geCfg.EstimateLimitF = true ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) @@ -306,7 +306,7 @@ func TestWrappedEvmEstimator(t *testing.T) { estimatedGasLimit := uint64(15) // same as provided limit lggr := logger.Test(t) dynamicFees := false // expect legacy fee data - geCfg.EstimateGasLimitF = true + geCfg.EstimateLimitF = true ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) @@ -331,7 +331,7 @@ func TestWrappedEvmEstimator(t *testing.T) { lggr := logger.Test(t) // expect legacy fee data dynamicFees := false - geCfg.EstimateGasLimitF = true + geCfg.EstimateLimitF = true ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(0), errors.New("something broke")).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) @@ -362,7 +362,7 @@ func TestWrappedEvmEstimator(t *testing.T) { lggr := logger.Test(t) // expect legacy fee data dynamicFees := false - geCfg.EstimateGasLimitF = true + geCfg.EstimateLimitF = true ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(estimatedGasLimit, nil).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) @@ -392,7 +392,7 @@ func TestWrappedEvmEstimator(t *testing.T) { lggr := logger.Test(t) // expect legacy fee data dynamicFees := false - geCfg.EstimateGasLimitF = true + geCfg.EstimateLimitF = true ethClient := testutils.NewEthClientMockWithDefaultChain(t) ethClient.On("EstimateGas", mock.Anything, mock.Anything).Return(uint64(0), errors.New("something broke")).Twice() estimator := gas.NewEvmFeeEstimator(lggr, getRootEst, dynamicFees, geCfg, ethClient) diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index 41f50f4434..514d533159 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -1676,7 +1676,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_GasEstimationError(t *testing.T) db := pgtest.NewSqlxDB(t) cfg := configtest.NewTestGeneralConfig(t) - cfg.EVMConfigs()[0].GasEstimator.EstimateGasLimit = ptr(true) // Enabled gas limit estimation + cfg.EVMConfigs()[0].GasEstimator.EstimateLimit = ptr(true) // Enabled gas limit estimation limitMultiplier := float32(1.25) cfg.EVMConfigs()[0].GasEstimator.LimitMultiplier = ptr(decimal.NewFromFloat32(limitMultiplier)) // Set LimitMultiplier for the buffer txStore := cltest.NewTestTxStore(t, db) diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index a4f326d480..963c608b53 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -89,7 +89,7 @@ func (g *TestGasEstimatorConfig) LimitTransfer() uint64 { return 42 } func (g *TestGasEstimatorConfig) PriceMax() *assets.Wei { return assets.NewWeiI(42) } func (g *TestGasEstimatorConfig) PriceMin() *assets.Wei { return assets.NewWeiI(42) } func (g *TestGasEstimatorConfig) Mode() string { return "FixedPrice" } -func (g *TestGasEstimatorConfig) EstimateGasLimit() bool { return false } +func (g *TestGasEstimatorConfig) EstimateLimit() bool { return false } func (g *TestGasEstimatorConfig) LimitJobType() evmconfig.LimitJobType { return &TestLimitJobTypeConfig{} } diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index 3e54d2cd0f..385432e12c 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -194,8 +194,8 @@ LimitMax = 8_000_000 # Default LimitMultiplier = '1.0' # Default # LimitTransfer is the gas limit used for an ordinary ETH transfer. LimitTransfer = 21_000 # Default -# EstimateGasLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound. -EstimateGasLimit = false # Default +# EstimateLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound. +EstimateLimit = false # Default # BumpMin is the minimum fixed amount of wei by which gas is bumped on each transaction attempt. BumpMin = '5 gwei' # Default # BumpPercent is the percentage by which to bump gas on a transaction that has exceeded `BumpThreshold`. The larger of `BumpPercent` and `BumpMin` is taken for gas bumps. diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 3ded57cf76..2aae93e45d 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -520,7 +520,7 @@ func TestConfig_Marshal(t *testing.T) { LimitMax: ptr[uint64](17), LimitMultiplier: mustDecimal("1.234"), LimitTransfer: ptr[uint64](100), - EstimateGasLimit: ptr(false), + EstimateLimit: ptr(false), TipCapDefault: assets.NewWeiI(2), TipCapMin: assets.NewWeiI(1), PriceDefault: assets.NewWeiI(math.MaxInt64), @@ -1025,7 +1025,7 @@ LimitDefault = 12 LimitMax = 17 LimitMultiplier = '1.234' LimitTransfer = 100 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 10 BumpThreshold = 6 diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index ea62617018..08f688dfac 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -316,7 +316,7 @@ LimitDefault = 12 LimitMax = 17 LimitMultiplier = '1.234' LimitTransfer = 100 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 10 BumpThreshold = 6 diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index dacb04fe9d..4de06be86d 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -303,7 +303,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -404,7 +404,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -499,7 +499,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index 668ca74ced..d046a2ab8b 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -316,7 +316,7 @@ LimitDefault = 12 LimitMax = 17 LimitMultiplier = '1.234' LimitTransfer = 100 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 10 BumpThreshold = 6 diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index 108dd401a7..df335e0474 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -303,7 +303,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -404,7 +404,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -499,7 +499,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 83d60debba..87e67701fc 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1818,7 +1818,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -1913,7 +1913,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2008,7 +2008,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2103,7 +2103,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2199,7 +2199,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -2294,7 +2294,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2389,7 +2389,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2485,7 +2485,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2580,7 +2580,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -2674,7 +2674,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2861,7 +2861,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -2956,7 +2956,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -3052,7 +3052,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3147,7 +3147,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -3242,7 +3242,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -3337,7 +3337,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -3432,7 +3432,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -3527,7 +3527,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3622,7 +3622,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -3717,7 +3717,7 @@ LimitDefault = 100000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3812,7 +3812,7 @@ LimitDefault = 2500000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -3907,7 +3907,7 @@ LimitDefault = 2500000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4003,7 +4003,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -4098,7 +4098,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4380,7 +4380,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -4475,7 +4475,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4570,7 +4570,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -4665,7 +4665,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4760,7 +4760,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -4854,7 +4854,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 0 @@ -4949,7 +4949,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -5044,7 +5044,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -5139,7 +5139,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '20 mwei' BumpPercent = 40 BumpThreshold = 3 @@ -5234,7 +5234,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5423,7 +5423,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5518,7 +5518,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -5613,7 +5613,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -5709,7 +5709,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5805,7 +5805,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -5996,7 +5996,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -6091,7 +6091,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '2 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6186,7 +6186,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6281,7 +6281,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6376,7 +6376,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '2 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6470,7 +6470,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 @@ -6564,7 +6564,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6658,7 +6658,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 @@ -6753,7 +6753,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -6942,7 +6942,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7036,7 +7036,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '20 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7226,7 +7226,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -7322,7 +7322,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -7418,7 +7418,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7514,7 +7514,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7610,7 +7610,7 @@ LimitDefault = 8000000 LimitMax = 1000000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 5 @@ -7705,7 +7705,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '1 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7800,7 +7800,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '1 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7895,7 +7895,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -7990,7 +7990,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '100 wei' BumpPercent = 20 BumpThreshold = 3 @@ -8180,7 +8180,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -8275,7 +8275,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 @@ -8638,7 +8638,7 @@ LimitDefault = 8_000_000 # Default LimitMax = 8_000_000 # Default LimitMultiplier = '1.0' # Default LimitTransfer = 21_000 # Default -EstimateGasLimit = false # Default +EstimateLimit = false # Default BumpMin = '5 gwei' # Default BumpPercent = 20 # Default BumpThreshold = 3 # Default @@ -8733,11 +8733,11 @@ LimitTransfer = 21_000 # Default ``` LimitTransfer is the gas limit used for an ordinary ETH transfer. -### EstimateGasLimit +### EstimateLimit ```toml -EstimateGasLimit = false # Default +EstimateLimit = false # Default ``` -EstimateGasLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound. +EstimateLimit enables estimating gas limits for transactions. This feature respects the gas limit provided during transaction creation as an upper bound. ### BumpMin ```toml diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index 135e062cf7..fbcb4725d6 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -359,7 +359,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar index 9fa53d50b6..5fa31dd1aa 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -359,7 +359,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index ad38875f18..832185c53f 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -359,7 +359,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 6baa4ce92c..972c6c10fc 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -349,7 +349,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index 985cf03970..c7b5261d26 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -356,7 +356,7 @@ LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 -EstimateGasLimit = false +EstimateLimit = false BumpMin = '5 gwei' BumpPercent = 20 BumpThreshold = 3 From d2cd08f1411761e77a0829a8a6ae4557b7ac7216 Mon Sep 17 00:00:00 2001 From: Dylan Tinianov Date: Thu, 8 Aug 2024 23:33:44 +0900 Subject: [PATCH 08/31] Add Mantle errors (#14053) * Add Mantle errors * Add tests for Mantle errors * changeset * Update seven-kiwis-run.md --- .changeset/seven-kiwis-run.md | 5 +++++ core/chains/evm/client/errors_test.go | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 .changeset/seven-kiwis-run.md diff --git a/.changeset/seven-kiwis-run.md b/.changeset/seven-kiwis-run.md new file mode 100644 index 0000000000..3b56117c46 --- /dev/null +++ b/.changeset/seven-kiwis-run.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Added custom client error messages for Mantle to capture InsufficientEth and Fatal errors. #added diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index bddc7dd3db..cee21c7ab4 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -215,6 +215,7 @@ func Test_Eth_Errors(t *testing.T) { {"insufficient funds for gas + value. balance: 42719769622667482000, fee: 48098250000000, value: 42719769622667482000", true, "celo"}, {"client error insufficient eth", true, "tomlConfig"}, {"transaction would cause overdraft", true, "Geth"}, + {"failed to forward tx to sequencer, please try again. Error message: 'insufficient funds for gas * price + value'", true, "Mantle"}, } for _, test := range tests { err = evmclient.NewSendErrorS(test.message) @@ -400,6 +401,8 @@ func Test_Eth_Errors_Fatal(t *testing.T) { {"Failed to serialize transaction: max priority fee per gas higher than 2^64-1", true, "zkSync"}, {"Failed to serialize transaction: oversized data. max: 1000000; actual: 1000000", true, "zkSync"}, + {"failed to forward tx to sequencer, please try again. Error message: 'invalid sender'", true, "Mantle"}, + {"client error fatal", true, "tomlConfig"}, } From 1493add1e5e6c31636aedf5e292837d071af9d18 Mon Sep 17 00:00:00 2001 From: Matthew Romage <33700623+ma33r@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:31:00 -0400 Subject: [PATCH 09/31] Changed Optimism L1 Oracle to support Mantle (#14160) * edited op stack oracle to include tokenRatio for Mantle * typo fix * fixed syntax errors * fixed compilation errors in test * added unit tests for Mantle oracle changes * typo fix * added changeset file * added hashtag to changeset * changed unit test to include new chaintype * changed test to include new chaintype * typo fix * addressed alphabetical order and error issues * used batch call instead of separate calls for L1 Base Fee * added test cases for RPC errors to Mantle --- .changeset/two-mugs-complain.md | 5 ++ core/chains/evm/config/chaintype/chaintype.go | 6 +- core/chains/evm/gas/rollups/l1_oracle.go | 4 +- core/chains/evm/gas/rollups/l1_oracle_abi.go | 1 + core/chains/evm/gas/rollups/op_l1_oracle.go | 87 +++++++++++++++++- .../evm/gas/rollups/op_l1_oracle_test.go | 88 +++++++++++++++++++ core/services/chainlink/config_test.go | 4 +- 7 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 .changeset/two-mugs-complain.md diff --git a/.changeset/two-mugs-complain.md b/.changeset/two-mugs-complain.md new file mode 100644 index 0000000000..77cdcbfe9e --- /dev/null +++ b/.changeset/two-mugs-complain.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Edited the Optimism Stack L1 Oracle to add support for Mantle #added diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go index 07ea620624..35dd214b1f 100644 --- a/core/chains/evm/config/chaintype/chaintype.go +++ b/core/chains/evm/config/chaintype/chaintype.go @@ -14,6 +14,7 @@ const ( ChainGnosis ChainType = "gnosis" ChainHedera ChainType = "hedera" ChainKroma ChainType = "kroma" + ChainMantle ChainType = "mantle" ChainMetis ChainType = "metis" ChainOptimismBedrock ChainType = "optimismBedrock" ChainScroll ChainType = "scroll" @@ -37,7 +38,7 @@ func (c ChainType) IsL2() bool { func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: + case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: return true } return false @@ -57,6 +58,8 @@ func ChainTypeFromSlug(slug string) ChainType { return ChainHedera case "kroma": return ChainKroma + case "mantle": + return ChainMantle case "metis": return ChainMetis case "optimismBedrock": @@ -129,6 +132,7 @@ var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Joi string(ChainGnosis), string(ChainHedera), string(ChainKroma), + string(ChainMantle), string(ChainMetis), string(ChainOptimismBedrock), string(ChainScroll), diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index f707fab684..e1249fdb7e 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -43,7 +43,7 @@ const ( PollPeriod = 6 * time.Second ) -var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync} +var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync, chaintype.ChainMantle} func IsRollupWithL1Support(chainType chaintype.ChainType) bool { return slices.Contains(supportedChainTypes, chainType) @@ -56,7 +56,7 @@ func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chai var l1Oracle L1Oracle var err error switch chainType { - case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll: + case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainMantle: l1Oracle, err = NewOpStackL1GasOracle(lggr, ethClient, chainType) case chaintype.ChainArbitrum: l1Oracle, err = NewArbitrumL1GasOracle(lggr, ethClient) diff --git a/core/chains/evm/gas/rollups/l1_oracle_abi.go b/core/chains/evm/gas/rollups/l1_oracle_abi.go index fa5d9c8539..848957ce53 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_abi.go +++ b/core/chains/evm/gas/rollups/l1_oracle_abi.go @@ -19,3 +19,4 @@ const OPBaseFeeScalarAbiString = `[{"inputs":[],"name":"baseFeeScalar","outputs" const OPBlobBaseFeeAbiString = `[{"inputs":[],"name":"blobBaseFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` const OPBlobBaseFeeScalarAbiString = `[{"inputs":[],"name":"blobBaseFeeScalar","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"}]` const OPDecimalsAbiString = `[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}]` +const MantleTokenRatioAbiString = `[{"inputs":[],"name":"tokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 6805cd7095..1b93f8fc3f 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -50,6 +50,7 @@ type optimismL1Oracle struct { blobBaseFeeCalldata []byte blobBaseFeeScalarCalldata []byte decimalsCalldata []byte + tokenRatioCalldata []byte isEcotoneCalldata []byte isEcotoneMethodAbi abi.ABI isFjordCalldata []byte @@ -87,7 +88,11 @@ const ( // decimals is a hex encoded call to: // `function decimals() public pure returns (uint256);` decimalsMethod = "decimals" - // OPGasOracleAddress is the address of the precompiled contract that exists on Optimism and Base. + // tokenRatio fetches the tokenRatio used for Mantle's gas price calculation + // tokenRatio is a hex encoded call to: + // `function tokenRatio() public pure returns (uint256);` + tokenRatioMethod = "tokenRatio" + // OPGasOracleAddress is the address of the precompiled contract that exists on Optimism, Base and Mantle. OPGasOracleAddress = "0x420000000000000000000000000000000000000F" // KromaGasOracleAddress is the address of the precompiled contract that exists on Kroma. KromaGasOracleAddress = "0x4200000000000000000000000000000000000005" @@ -98,7 +103,7 @@ const ( func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (*optimismL1Oracle, error) { var precompileAddress string switch chainType { - case chaintype.ChainOptimismBedrock: + case chaintype.ChainOptimismBedrock, chaintype.ChainMantle: precompileAddress = OPGasOracleAddress case chaintype.ChainKroma: precompileAddress = KromaGasOracleAddress @@ -187,6 +192,16 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", decimalsMethod, chainType, err) } + // Encode calldata for tokenRatio method + tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(MantleTokenRatioAbiString)) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", tokenRatioMethod, chainType, err) + } + tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", tokenRatioMethod, chainType, err) + } + return &optimismL1Oracle{ client: ethClient, pollPeriod: PollPeriod, @@ -208,6 +223,7 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy blobBaseFeeCalldata: blobBaseFeeCalldata, blobBaseFeeScalarCalldata: blobBaseFeeScalarCalldata, decimalsCalldata: decimalsCalldata, + tokenRatioCalldata: tokenRatioCalldata, isEcotoneCalldata: isEcotoneCalldata, isEcotoneMethodAbi: isEcotoneMethodAbi, isFjordCalldata: isFjordCalldata, @@ -346,6 +362,10 @@ func (o *optimismL1Oracle) GetGasCost(ctx context.Context, tx *gethtypes.Transac } func (o *optimismL1Oracle) GetDAGasPrice(ctx context.Context) (*big.Int, error) { + if o.chainType == chaintype.ChainMantle { + return o.getMantleGasPrice(ctx) + } + err := o.checkForUpgrade(ctx) if err != nil { return nil, err @@ -443,6 +463,69 @@ func (o *optimismL1Oracle) getV1GasPrice(ctx context.Context) (*big.Int, error) return new(big.Int).SetBytes(b), nil } +// Returns the gas price for Mantle. The formula is the same as Optimism Bedrock (getV1GasPrice), but the tokenRatio parameter is multiplied +func (o *optimismL1Oracle) getMantleGasPrice(ctx context.Context) (*big.Int, error) { + // call oracle to get l1BaseFee and tokenRatio + rpcBatchCalls := []rpc.BatchElem{ + { + Method: "eth_call", + Args: []any{ + map[string]interface{}{ + "from": common.Address{}, + "to": o.l1OracleAddress, + "data": hexutil.Bytes(o.l1BaseFeeCalldata), + }, + "latest", + }, + Result: new(string), + }, + { + Method: "eth_call", + Args: []any{ + map[string]interface{}{ + "from": common.Address{}, + "to": o.l1OracleAddress, + "data": hexutil.Bytes(o.tokenRatioCalldata), + }, + "latest", + }, + Result: new(string), + }, + } + + err := o.client.BatchCallContext(ctx, rpcBatchCalls) + if err != nil { + return nil, fmt.Errorf("fetch gas price parameters batch call failed: %w", err) + } + if rpcBatchCalls[0].Error != nil { + return nil, fmt.Errorf("%s call failed in a batch: %w", l1BaseFeeMethod, err) + } + if rpcBatchCalls[1].Error != nil { + return nil, fmt.Errorf("%s call failed in a batch: %w", tokenRatioMethod, err) + } + + // Extract values from responses + l1BaseFeeResult := *(rpcBatchCalls[0].Result.(*string)) + tokenRatioResult := *(rpcBatchCalls[1].Result.(*string)) + + // Decode the responses into bytes + l1BaseFeeBytes, err := hexutil.Decode(l1BaseFeeResult) + if err != nil { + return nil, fmt.Errorf("failed to decode %s rpc result: %w", l1BaseFeeMethod, err) + } + tokenRatioBytes, err := hexutil.Decode(tokenRatioResult) + if err != nil { + return nil, fmt.Errorf("failed to decode %s rpc result: %w", tokenRatioMethod, err) + } + + // Convert bytes to big int for calculations + l1BaseFee := new(big.Int).SetBytes(l1BaseFeeBytes) + tokenRatio := new(big.Int).SetBytes(tokenRatioBytes) + + // multiply l1BaseFee and tokenRatio and return + return new(big.Int).Mul(l1BaseFee, tokenRatio), nil +} + // Returns the scaled gas price using baseFeeScalar, l1BaseFee, blobBaseFeeScalar, and blobBaseFee fields from the oracle // Confirmed the same calculation is used to determine gas price for both Ecotone and Fjord func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.Int, error) { diff --git a/core/chains/evm/gas/rollups/op_l1_oracle_test.go b/core/chains/evm/gas/rollups/op_l1_oracle_test.go index f5f009f1ea..88bb96534d 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -111,6 +111,94 @@ func TestOPL1Oracle_ReadV1GasPrice(t *testing.T) { } } +func TestOPL1Oracle_ReadMantleGasPrice(t *testing.T) { + l1BaseFee := big.NewInt(100) + tokenRatio := big.NewInt(40) + oracleAddress := common.HexToAddress("0x1234").String() + + t.Parallel() + t.Run("correctly fetches gas price if chain is Mantle", func(t *testing.T) { + // Encode calldata for l1BaseFee method + l1BaseFeeMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) + require.NoError(t, err) + l1BaseFeeCalldata, err := l1BaseFeeMethodAbi.Pack(l1BaseFeeMethod) + require.NoError(t, err) + + // Encode calldata for tokenRatio method + tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(MantleTokenRatioAbiString)) + require.NoError(t, err) + tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) + require.NoError(t, err) + + ethClient := mocks.NewL1OracleClient(t) + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { + rpcElements := args.Get(1).([]rpc.BatchElem) + require.Equal(t, 2, len(rpcElements)) + for _, rE := range rpcElements { + require.Equal(t, "eth_call", rE.Method) + require.Equal(t, oracleAddress, rE.Args[0].(map[string]interface{})["to"]) + require.Equal(t, "latest", rE.Args[1]) + } + require.Equal(t, hexutil.Bytes(l1BaseFeeCalldata), rpcElements[0].Args[0].(map[string]interface{})["data"]) + require.Equal(t, hexutil.Bytes(tokenRatioCalldata), rpcElements[1].Args[0].(map[string]interface{})["data"]) + + res1 := common.BigToHash(l1BaseFee).Hex() + res2 := common.BigToHash(tokenRatio).Hex() + + rpcElements[0].Result = &res1 + rpcElements[1].Result = &res2 + }).Return(nil).Once() + + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) + require.NoError(t, err) + + gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) + require.NoError(t, err) + + assert.Equal(t, new(big.Int).Mul(l1BaseFee, tokenRatio), gasPrice) + }) + + t.Run("fetching Mantle price but rpc returns bad data", func(t *testing.T) { + ethClient := mocks.NewL1OracleClient(t) + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { + rpcElements := args.Get(1).([]rpc.BatchElem) + var badData = "zzz" + rpcElements[0].Result = &badData + rpcElements[1].Result = &badData + }).Return(nil).Once() + + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) + require.NoError(t, err) + _, err = oracle.GetDAGasPrice(tests.Context(t)) + assert.Error(t, err) + }) + + t.Run("fetching Mantle price but rpc parent call errors", func(t *testing.T) { + ethClient := mocks.NewL1OracleClient(t) + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Return(fmt.Errorf("revert")).Once() + + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) + require.NoError(t, err) + _, err = oracle.GetDAGasPrice(tests.Context(t)) + assert.Error(t, err) + }) + + t.Run("fetching Mantle price but one of the sub rpc call errors", func(t *testing.T) { + ethClient := mocks.NewL1OracleClient(t) + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { + rpcElements := args.Get(1).([]rpc.BatchElem) + res := common.BigToHash(l1BaseFee).Hex() + rpcElements[0].Result = &res + rpcElements[1].Error = fmt.Errorf("revert") + }).Return(nil).Once() + + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) + require.NoError(t, err) + _, err = oracle.GetDAGasPrice(tests.Context(t)) + assert.Error(t, err) + }) +} + func setupUpgradeCheck(t *testing.T, oracleAddress string, isFjord, isEcotone bool) *mocks.L1OracleClient { trueHex := "0x0000000000000000000000000000000000000000000000000000000000000001" falseHex := "0x0000000000000000000000000000000000000000000000000000000000000000" diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 2aae93e45d..76150d2680 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1306,7 +1306,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 10 errors: - ChainType: invalid value (Foo): must not be set with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - HeadTracker.HistoryDepth: invalid value (30): must be greater than or equal to FinalizedBlockOffset - GasEstimator.BumpThreshold: invalid value (0): cannot be 0 if auto-purge feature is enabled for Foo - Transactions.AutoPurge.Threshold: missing: needs to be set if auto-purge feature is enabled for Foo @@ -1319,7 +1319,7 @@ func TestConfig_Validate(t *testing.T) { - 2: 5 errors: - ChainType: invalid value (Arbitrum): only "optimismBedrock" can be used with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - FinalityDepth: invalid value (0): must be greater than or equal to 1 - MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1 - 3.Nodes: 5 errors: From 57e10f62579604500f942eb4c81c161932b89a0f Mon Sep 17 00:00:00 2001 From: Chunkai Yang Date: Wed, 18 Sep 2024 10:07:05 -0400 Subject: [PATCH 10/31] Mantle use vanilla l1 oracle (#14471) * make Mantle use vanilla OP gas price oracle * changeset * bring back l1 oracle * update mock * [BCI-4072] changeset update --------- Co-authored-by: valerii.kabisov Co-authored-by: Valerii Kabisov <172247313+valerii-kabisov-cll@users.noreply.github.com> --- .changeset/curly-onions-tell.md | 5 ++ .../evm/gas/rollups/arbitrum_l1_oracle.go | 4 + core/chains/evm/gas/rollups/l1_oracle.go | 1 + core/chains/evm/gas/rollups/l1_oracle_abi.go | 1 - .../chains/evm/gas/rollups/mocks/l1_oracle.go | 48 ++++++++++ core/chains/evm/gas/rollups/op_l1_oracle.go | 86 +----------------- .../evm/gas/rollups/op_l1_oracle_test.go | 88 ------------------- .../evm/gas/rollups/zkSync_l1_oracle.go | 4 + 8 files changed, 66 insertions(+), 171 deletions(-) create mode 100644 .changeset/curly-onions-tell.md diff --git a/.changeset/curly-onions-tell.md b/.changeset/curly-onions-tell.md new file mode 100644 index 0000000000..249f616c01 --- /dev/null +++ b/.changeset/curly-onions-tell.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#changed Make Mantle use default OP stack l1 gas oracle in core diff --git a/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go b/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go index c01244db70..d758dc711e 100644 --- a/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go +++ b/core/chains/evm/gas/rollups/arbitrum_l1_oracle.go @@ -112,6 +112,10 @@ func (o *arbitrumL1Oracle) Name() string { return o.logger.Name() } +func (o *arbitrumL1Oracle) ChainType(_ context.Context) chaintype.ChainType { + return o.chainType +} + func (o *arbitrumL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run() diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index e1249fdb7e..4195175598 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -26,6 +26,7 @@ type L1Oracle interface { GasPrice(ctx context.Context) (*assets.Wei, error) GetGasCost(ctx context.Context, tx *types.Transaction, blockNum *big.Int) (*assets.Wei, error) + ChainType(ctx context.Context) chaintype.ChainType } type l1OracleClient interface { diff --git a/core/chains/evm/gas/rollups/l1_oracle_abi.go b/core/chains/evm/gas/rollups/l1_oracle_abi.go index 848957ce53..fa5d9c8539 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_abi.go +++ b/core/chains/evm/gas/rollups/l1_oracle_abi.go @@ -19,4 +19,3 @@ const OPBaseFeeScalarAbiString = `[{"inputs":[],"name":"baseFeeScalar","outputs" const OPBlobBaseFeeAbiString = `[{"inputs":[],"name":"blobBaseFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` const OPBlobBaseFeeScalarAbiString = `[{"inputs":[],"name":"blobBaseFeeScalar","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"}]` const OPDecimalsAbiString = `[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}]` -const MantleTokenRatioAbiString = `[{"inputs":[],"name":"tokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` diff --git a/core/chains/evm/gas/rollups/mocks/l1_oracle.go b/core/chains/evm/gas/rollups/mocks/l1_oracle.go index e82cb4ee90..25bb3a2db5 100644 --- a/core/chains/evm/gas/rollups/mocks/l1_oracle.go +++ b/core/chains/evm/gas/rollups/mocks/l1_oracle.go @@ -7,6 +7,8 @@ import ( assets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + chaintype "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + context "context" mock "github.com/stretchr/testify/mock" @@ -27,6 +29,52 @@ func (_m *L1Oracle) EXPECT() *L1Oracle_Expecter { return &L1Oracle_Expecter{mock: &_m.Mock} } +// ChainType provides a mock function with given fields: ctx +func (_m *L1Oracle) ChainType(ctx context.Context) chaintype.ChainType { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ChainType") + } + + var r0 chaintype.ChainType + if rf, ok := ret.Get(0).(func(context.Context) chaintype.ChainType); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(chaintype.ChainType) + } + + return r0 +} + +// L1Oracle_ChainType_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChainType' +type L1Oracle_ChainType_Call struct { + *mock.Call +} + +// ChainType is a helper method to define mock.On call +// - ctx context.Context +func (_e *L1Oracle_Expecter) ChainType(ctx interface{}) *L1Oracle_ChainType_Call { + return &L1Oracle_ChainType_Call{Call: _e.mock.On("ChainType", ctx)} +} + +func (_c *L1Oracle_ChainType_Call) Run(run func(ctx context.Context)) *L1Oracle_ChainType_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *L1Oracle_ChainType_Call) Return(_a0 chaintype.ChainType) *L1Oracle_ChainType_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *L1Oracle_ChainType_Call) RunAndReturn(run func(context.Context) chaintype.ChainType) *L1Oracle_ChainType_Call { + _c.Call.Return(run) + return _c +} + // Close provides a mock function with given fields: func (_m *L1Oracle) Close() error { ret := _m.Called() diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 1b93f8fc3f..11babc5ca5 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -88,10 +88,6 @@ const ( // decimals is a hex encoded call to: // `function decimals() public pure returns (uint256);` decimalsMethod = "decimals" - // tokenRatio fetches the tokenRatio used for Mantle's gas price calculation - // tokenRatio is a hex encoded call to: - // `function tokenRatio() public pure returns (uint256);` - tokenRatioMethod = "tokenRatio" // OPGasOracleAddress is the address of the precompiled contract that exists on Optimism, Base and Mantle. OPGasOracleAddress = "0x420000000000000000000000000000000000000F" // KromaGasOracleAddress is the address of the precompiled contract that exists on Kroma. @@ -192,16 +188,6 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", decimalsMethod, chainType, err) } - // Encode calldata for tokenRatio method - tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(MantleTokenRatioAbiString)) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", tokenRatioMethod, chainType, err) - } - tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) - if err != nil { - return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", tokenRatioMethod, chainType, err) - } - return &optimismL1Oracle{ client: ethClient, pollPeriod: PollPeriod, @@ -223,7 +209,6 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy blobBaseFeeCalldata: blobBaseFeeCalldata, blobBaseFeeScalarCalldata: blobBaseFeeScalarCalldata, decimalsCalldata: decimalsCalldata, - tokenRatioCalldata: tokenRatioCalldata, isEcotoneCalldata: isEcotoneCalldata, isEcotoneMethodAbi: isEcotoneMethodAbi, isFjordCalldata: isFjordCalldata, @@ -235,6 +220,10 @@ func (o *optimismL1Oracle) Name() string { return o.logger.Name() } +func (o *optimismL1Oracle) ChainType(_ context.Context) chaintype.ChainType { + return o.chainType +} + func (o *optimismL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run() @@ -362,10 +351,6 @@ func (o *optimismL1Oracle) GetGasCost(ctx context.Context, tx *gethtypes.Transac } func (o *optimismL1Oracle) GetDAGasPrice(ctx context.Context) (*big.Int, error) { - if o.chainType == chaintype.ChainMantle { - return o.getMantleGasPrice(ctx) - } - err := o.checkForUpgrade(ctx) if err != nil { return nil, err @@ -463,69 +448,6 @@ func (o *optimismL1Oracle) getV1GasPrice(ctx context.Context) (*big.Int, error) return new(big.Int).SetBytes(b), nil } -// Returns the gas price for Mantle. The formula is the same as Optimism Bedrock (getV1GasPrice), but the tokenRatio parameter is multiplied -func (o *optimismL1Oracle) getMantleGasPrice(ctx context.Context) (*big.Int, error) { - // call oracle to get l1BaseFee and tokenRatio - rpcBatchCalls := []rpc.BatchElem{ - { - Method: "eth_call", - Args: []any{ - map[string]interface{}{ - "from": common.Address{}, - "to": o.l1OracleAddress, - "data": hexutil.Bytes(o.l1BaseFeeCalldata), - }, - "latest", - }, - Result: new(string), - }, - { - Method: "eth_call", - Args: []any{ - map[string]interface{}{ - "from": common.Address{}, - "to": o.l1OracleAddress, - "data": hexutil.Bytes(o.tokenRatioCalldata), - }, - "latest", - }, - Result: new(string), - }, - } - - err := o.client.BatchCallContext(ctx, rpcBatchCalls) - if err != nil { - return nil, fmt.Errorf("fetch gas price parameters batch call failed: %w", err) - } - if rpcBatchCalls[0].Error != nil { - return nil, fmt.Errorf("%s call failed in a batch: %w", l1BaseFeeMethod, err) - } - if rpcBatchCalls[1].Error != nil { - return nil, fmt.Errorf("%s call failed in a batch: %w", tokenRatioMethod, err) - } - - // Extract values from responses - l1BaseFeeResult := *(rpcBatchCalls[0].Result.(*string)) - tokenRatioResult := *(rpcBatchCalls[1].Result.(*string)) - - // Decode the responses into bytes - l1BaseFeeBytes, err := hexutil.Decode(l1BaseFeeResult) - if err != nil { - return nil, fmt.Errorf("failed to decode %s rpc result: %w", l1BaseFeeMethod, err) - } - tokenRatioBytes, err := hexutil.Decode(tokenRatioResult) - if err != nil { - return nil, fmt.Errorf("failed to decode %s rpc result: %w", tokenRatioMethod, err) - } - - // Convert bytes to big int for calculations - l1BaseFee := new(big.Int).SetBytes(l1BaseFeeBytes) - tokenRatio := new(big.Int).SetBytes(tokenRatioBytes) - - // multiply l1BaseFee and tokenRatio and return - return new(big.Int).Mul(l1BaseFee, tokenRatio), nil -} - // Returns the scaled gas price using baseFeeScalar, l1BaseFee, blobBaseFeeScalar, and blobBaseFee fields from the oracle // Confirmed the same calculation is used to determine gas price for both Ecotone and Fjord func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.Int, error) { diff --git a/core/chains/evm/gas/rollups/op_l1_oracle_test.go b/core/chains/evm/gas/rollups/op_l1_oracle_test.go index 88bb96534d..f5f009f1ea 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -111,94 +111,6 @@ func TestOPL1Oracle_ReadV1GasPrice(t *testing.T) { } } -func TestOPL1Oracle_ReadMantleGasPrice(t *testing.T) { - l1BaseFee := big.NewInt(100) - tokenRatio := big.NewInt(40) - oracleAddress := common.HexToAddress("0x1234").String() - - t.Parallel() - t.Run("correctly fetches gas price if chain is Mantle", func(t *testing.T) { - // Encode calldata for l1BaseFee method - l1BaseFeeMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) - require.NoError(t, err) - l1BaseFeeCalldata, err := l1BaseFeeMethodAbi.Pack(l1BaseFeeMethod) - require.NoError(t, err) - - // Encode calldata for tokenRatio method - tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(MantleTokenRatioAbiString)) - require.NoError(t, err) - tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) - require.NoError(t, err) - - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { - rpcElements := args.Get(1).([]rpc.BatchElem) - require.Equal(t, 2, len(rpcElements)) - for _, rE := range rpcElements { - require.Equal(t, "eth_call", rE.Method) - require.Equal(t, oracleAddress, rE.Args[0].(map[string]interface{})["to"]) - require.Equal(t, "latest", rE.Args[1]) - } - require.Equal(t, hexutil.Bytes(l1BaseFeeCalldata), rpcElements[0].Args[0].(map[string]interface{})["data"]) - require.Equal(t, hexutil.Bytes(tokenRatioCalldata), rpcElements[1].Args[0].(map[string]interface{})["data"]) - - res1 := common.BigToHash(l1BaseFee).Hex() - res2 := common.BigToHash(tokenRatio).Hex() - - rpcElements[0].Result = &res1 - rpcElements[1].Result = &res2 - }).Return(nil).Once() - - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) - require.NoError(t, err) - - gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) - require.NoError(t, err) - - assert.Equal(t, new(big.Int).Mul(l1BaseFee, tokenRatio), gasPrice) - }) - - t.Run("fetching Mantle price but rpc returns bad data", func(t *testing.T) { - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { - rpcElements := args.Get(1).([]rpc.BatchElem) - var badData = "zzz" - rpcElements[0].Result = &badData - rpcElements[1].Result = &badData - }).Return(nil).Once() - - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) - require.NoError(t, err) - _, err = oracle.GetDAGasPrice(tests.Context(t)) - assert.Error(t, err) - }) - - t.Run("fetching Mantle price but rpc parent call errors", func(t *testing.T) { - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Return(fmt.Errorf("revert")).Once() - - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) - require.NoError(t, err) - _, err = oracle.GetDAGasPrice(tests.Context(t)) - assert.Error(t, err) - }) - - t.Run("fetching Mantle price but one of the sub rpc call errors", func(t *testing.T) { - ethClient := mocks.NewL1OracleClient(t) - ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { - rpcElements := args.Get(1).([]rpc.BatchElem) - res := common.BigToHash(l1BaseFee).Hex() - rpcElements[0].Result = &res - rpcElements[1].Error = fmt.Errorf("revert") - }).Return(nil).Once() - - oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) - require.NoError(t, err) - _, err = oracle.GetDAGasPrice(tests.Context(t)) - assert.Error(t, err) - }) -} - func setupUpgradeCheck(t *testing.T, oracleAddress string, isFjord, isEcotone bool) *mocks.L1OracleClient { trueHex := "0x0000000000000000000000000000000000000000000000000000000000000001" falseHex := "0x0000000000000000000000000000000000000000000000000000000000000000" diff --git a/core/chains/evm/gas/rollups/zkSync_l1_oracle.go b/core/chains/evm/gas/rollups/zkSync_l1_oracle.go index 31d93bc587..c294123354 100644 --- a/core/chains/evm/gas/rollups/zkSync_l1_oracle.go +++ b/core/chains/evm/gas/rollups/zkSync_l1_oracle.go @@ -83,6 +83,10 @@ func (o *zkSyncL1Oracle) Name() string { return o.logger.Name() } +func (o *zkSyncL1Oracle) ChainType(_ context.Context) chaintype.ChainType { + return o.chainType +} + func (o *zkSyncL1Oracle) Start(ctx context.Context) error { return o.StartOnce(o.Name(), func() error { go o.run() From 74ed3a2d97f97ff5496b94db34f6fb62c1a7dd80 Mon Sep 17 00:00:00 2001 From: Dimitris Grigoriou Date: Wed, 4 Sep 2024 11:13:55 +0900 Subject: [PATCH 11/31] FeeHistory estimator (#13833) * Introduce universal estimator * Fixes * Use WeiMin to cap bump price * Update connectivity logic * Fix error * Fixes * Cover an edge case when enforcing limits * Add changeset * Update mempool check logic * Update config names * Convert Universal Estimator to service * Client changes to support UE * Introduce configs * Update mocks * Fix lint * Fix test cases * Fix mockery * Fix test cases * Update comment * Fix Start/Close sync issue * Address feedback * Fix lint * Fix lint * More changes * Add more comments * Fix merge conflicts * Update CONFIG * Rename to FeeHistory estimator * Rename * Exclude zero priced priority fees * Remove HasMempool * Remove testing commit * Fixes * Add DefaultJitter * Add optimizations * Fix testscripts * Fix name * Update error messages --- .changeset/shiny-hornets-pretend.md | 5 + .mockery.yaml | 3 + common/fee/models.go | 2 +- common/fee/utils.go | 2 +- .../ocrimpls/contract_transmitter_test.go | 8 + core/chains/evm/client/chain_client.go | 9 + core/chains/evm/client/mocks/client.go | 60 ++ core/chains/evm/client/mocks/rpc_client.go | 60 ++ core/chains/evm/client/null_client.go | 4 + core/chains/evm/client/rpc_client.go | 25 + .../evm/client/simulated_backend_client.go | 4 + .../evm/config/chain_scoped_gas_estimator.go | 14 + core/chains/evm/config/config.go | 5 + core/chains/evm/config/config_test.go | 7 + core/chains/evm/config/mocks/gas_estimator.go | 47 ++ core/chains/evm/config/toml/config.go | 12 + .../evm/config/toml/defaults/fallback.toml | 3 + core/chains/evm/gas/fee_history_estimator.go | 440 +++++++++++++++ .../evm/gas/fee_history_estimator_test.go | 513 ++++++++++++++++++ .../evm/gas/mocks/fee_estimator_client.go | 118 ++++ .../gas/mocks/fee_history_estimator_client.go | 157 ++++++ core/chains/evm/gas/models.go | 14 + core/chains/evm/txmgr/test_helpers.go | 10 + core/config/docs/chains-evm.toml | 9 + core/services/chainlink/config_test.go | 6 + .../chainlink/testdata/config-full.toml | 3 + .../config-multi-chain-effective.toml | 9 + core/web/resolver/testdata/config-full.toml | 3 + .../config-multi-chain-effective.toml | 9 + docs/CONFIG.md | 201 +++++++ .../disk-based-logging-disabled.txtar | 3 + .../validate/disk-based-logging-no-dir.txtar | 3 + .../node/validate/disk-based-logging.txtar | 3 + testdata/scripts/node/validate/invalid.txtar | 3 + testdata/scripts/node/validate/valid.txtar | 3 + 35 files changed, 1775 insertions(+), 2 deletions(-) create mode 100644 .changeset/shiny-hornets-pretend.md create mode 100644 core/chains/evm/gas/fee_history_estimator.go create mode 100644 core/chains/evm/gas/fee_history_estimator_test.go create mode 100644 core/chains/evm/gas/mocks/fee_history_estimator_client.go diff --git a/.changeset/shiny-hornets-pretend.md b/.changeset/shiny-hornets-pretend.md new file mode 100644 index 0000000000..ff9946f4e7 --- /dev/null +++ b/.changeset/shiny-hornets-pretend.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Introduce new gas estimator #internal diff --git a/.mockery.yaml b/.mockery.yaml index 7fb335620c..39e7477a6d 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -65,6 +65,9 @@ packages: feeEstimatorClient: config: mockname: FeeEstimatorClient + feeHistoryEstimatorClient: + config: + mockname: FeeHistoryEstimatorClient EvmEstimator: github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups: interfaces: diff --git a/common/fee/models.go b/common/fee/models.go index 0496d3929c..0cc479d356 100644 --- a/common/fee/models.go +++ b/common/fee/models.go @@ -64,7 +64,7 @@ func CalculateBumpedFee( // Returns highest bumped fee price of originalFeePrice bumped by fixed units or percentage. func MaxBumpedFee(originalFeePrice *big.Int, feeBumpPercent uint16, feeBumpUnits *big.Int) *big.Int { return bigmath.Max( - addPercentage(originalFeePrice, feeBumpPercent), + AddPercentage(originalFeePrice, feeBumpPercent), new(big.Int).Add(originalFeePrice, feeBumpUnits), ) } diff --git a/common/fee/utils.go b/common/fee/utils.go index 26323e11e2..3d4b001e83 100644 --- a/common/fee/utils.go +++ b/common/fee/utils.go @@ -18,7 +18,7 @@ func ApplyMultiplier(feeLimit uint64, multiplier float32) (uint64, error) { } // Returns the input value increased by the given percentage. -func addPercentage(value *big.Int, percentage uint16) *big.Int { +func AddPercentage(value *big.Int, percentage uint16) *big.Int { bumped := new(big.Int) bumped.Mul(value, big.NewInt(int64(100+percentage))) bumped.Div(bumped, big.NewInt(100)) diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index 62da2f188f..1726405e22 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -590,6 +590,10 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } +func (g *TestGasEstimatorConfig) FeeHistory() evmconfig.FeeHistory { + return &TestFeeHistoryConfig{} +} + func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 1e6 } func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 2 } @@ -638,6 +642,10 @@ func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42 func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 } +type TestFeeHistoryConfig struct { + evmconfig.FeeHistory +} + type transactionsConfig struct { evmconfig.Transactions e *TestEvmConfig diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index c27d294ebf..310528424d 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -84,6 +84,7 @@ type Client interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) SuggestGasTipCap(ctx context.Context) (*big.Int, error) LatestBlockHeight(ctx context.Context) (*big.Int, error) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) HeaderByNumber(ctx context.Context, n *big.Int) (*types.Header, error) HeaderByHash(ctx context.Context, h common.Hash) (*types.Header, error) @@ -353,6 +354,14 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head, return c.multiNode.LatestFinalizedBlock(ctx) } +func (c *chainClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + rpc, err := c.multiNode.SelectNodeRPC() + if err != nil { + return feeHistory, err + } + return rpc.FeeHistory(ctx, blockCount, rewardPercentiles) +} + func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { msg := ethereum.CallMsg{ From: from, diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index 7b5220033b..da034d9577 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -780,6 +780,66 @@ func (_c *Client_EstimateGas_Call) RunAndReturn(run func(context.Context, ethere return _c } +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *Client) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' +type Client_FeeHistory_Call struct { + *mock.Call +} + +// FeeHistory is a helper method to define mock.On call +// - ctx context.Context +// - blockCount uint64 +// - rewardPercentiles []float64 +func (_e *Client_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *Client_FeeHistory_Call { + return &Client_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +} + +func (_c *Client_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *Client_FeeHistory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + }) + return _c +} + +func (_c *Client_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *Client_FeeHistory_Call { + _c.Call.Return(feeHistory, err) + return _c +} + +func (_c *Client_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *Client_FeeHistory_Call { + _c.Call.Return(run) + return _c +} + // FilterLogs provides a mock function with given fields: ctx, q func (_m *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { ret := _m.Called(ctx, q) diff --git a/core/chains/evm/client/mocks/rpc_client.go b/core/chains/evm/client/mocks/rpc_client.go index 06f79efd55..5567b3f897 100644 --- a/core/chains/evm/client/mocks/rpc_client.go +++ b/core/chains/evm/client/mocks/rpc_client.go @@ -889,6 +889,66 @@ func (_c *RPCClient_EstimateGas_Call) RunAndReturn(run func(context.Context, int return _c } +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *RPCClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RPCClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' +type RPCClient_FeeHistory_Call struct { + *mock.Call +} + +// FeeHistory is a helper method to define mock.On call +// - ctx context.Context +// - blockCount uint64 +// - rewardPercentiles []float64 +func (_e *RPCClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *RPCClient_FeeHistory_Call { + return &RPCClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +} + +func (_c *RPCClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *RPCClient_FeeHistory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + }) + return _c +} + +func (_c *RPCClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *RPCClient_FeeHistory_Call { + _c.Call.Return(feeHistory, err) + return _c +} + +func (_c *RPCClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *RPCClient_FeeHistory_Call { + _c.Call.Return(run) + return _c +} + // FilterEvents provides a mock function with given fields: ctx, query func (_m *RPCClient) FilterEvents(ctx context.Context, query ethereum.FilterQuery) ([]coretypes.Log, error) { ret := _m.Called(ctx, query) diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index 3129bcff9b..5b1a4d7e1b 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -235,3 +235,7 @@ func (nc *NullClient) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ common.Address, _ []byte) *SendError { return nil } + +func (nc *NullClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + return nil, nil +} diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index 72071199d4..295e24f7c9 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -103,6 +103,7 @@ type RPCClient interface { SuggestGasTipCap(ctx context.Context) (t *big.Int, err error) TransactionReceiptGeth(ctx context.Context, txHash common.Hash) (r *types.Receipt, err error) GetInterceptedChainInfo() (latest, highestUserObservations commonclient.ChainInfo) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) } const rpcSubscriptionMethodNewHeads = "newHeads" @@ -599,6 +600,7 @@ func (r *rpcClient) TransactionReceiptGeth(ctx context.Context, txHash common.Ha return } + func (r *rpcClient) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, err error) { ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx, r.rpcTimeout) defer cancel() @@ -1119,6 +1121,29 @@ func (r *rpcClient) BalanceAt(ctx context.Context, account common.Address, block return } +func (r *rpcClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + ctx, cancel, ws, http := r.makeLiveQueryCtxAndSafeGetClients(ctx, r.rpcTimeout) + defer cancel() + lggr := r.newRqLggr().With("blockCount", blockCount, "rewardPercentiles", rewardPercentiles) + + lggr.Debug("RPC call: evmclient.Client#FeeHistory") + start := time.Now() + if http != nil { + feeHistory, err = http.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles) + err = r.wrapHTTP(err) + } else { + feeHistory, err = ws.geth.FeeHistory(ctx, blockCount, nil, rewardPercentiles) + err = r.wrapWS(err) + } + duration := time.Since(start) + + r.logResult(lggr, err, duration, r.getRPCDomain(), "FeeHistory", + "feeHistory", feeHistory, + ) + + return +} + // CallArgs represents the data used to call the balance method of a contract. // "To" is the address of the ERC contract. "Data" is the message sent // to the contract. "From" is the sender address. diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 7dfd39f444..3ec1bff577 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -156,6 +156,10 @@ func (c *SimulatedBackendClient) LINKBalance(ctx context.Context, address common panic("not implemented") } +func (c *SimulatedBackendClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) { + panic("not implemented") +} + // TransactionReceipt returns the transaction receipt for the given transaction hash. func (c *SimulatedBackendClient) TransactionReceipt(ctx context.Context, receipt common.Hash) (*types.Receipt, error) { return c.b.TransactionReceipt(ctx, receipt) diff --git a/core/chains/evm/config/chain_scoped_gas_estimator.go b/core/chains/evm/config/chain_scoped_gas_estimator.go index 6f43839b01..54c7c36063 100644 --- a/core/chains/evm/config/chain_scoped_gas_estimator.go +++ b/core/chains/evm/config/chain_scoped_gas_estimator.go @@ -1,6 +1,8 @@ package config import ( + "time" + gethcommon "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -36,6 +38,10 @@ func (g *gasEstimatorConfig) BlockHistory() BlockHistory { return &blockHistoryConfig{c: g.c.BlockHistory, blockDelay: g.blockDelay, bumpThreshold: g.c.BumpThreshold} } +func (g *gasEstimatorConfig) FeeHistory() FeeHistory { + return &feeHistoryConfig{c: g.c.FeeHistory} +} + func (g *gasEstimatorConfig) EIP1559DynamicFees() bool { return *g.c.EIP1559DynamicFees } @@ -176,3 +182,11 @@ func (b *blockHistoryConfig) TransactionPercentile() uint16 { func (b *blockHistoryConfig) BlockDelay() uint16 { return *b.blockDelay } + +type feeHistoryConfig struct { + c toml.FeeHistoryEstimator +} + +func (u *feeHistoryConfig) CacheTimeout() time.Duration { + return u.c.CacheTimeout.Duration() +} diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index 9237cf5e1e..3d00fe86a4 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -117,6 +117,7 @@ type AutoPurgeConfig interface { type GasEstimator interface { BlockHistory() BlockHistory + FeeHistory() FeeHistory LimitJobType() LimitJobType EIP1559DynamicFees() bool @@ -158,6 +159,10 @@ type BlockHistory interface { TransactionPercentile() uint16 } +type FeeHistory interface { + CacheTimeout() time.Duration +} + type Workflow interface { FromAddress() *types.EIP55Address ForwarderAddress() *types.EIP55Address diff --git a/core/chains/evm/config/config_test.go b/core/chains/evm/config/config_test.go index 305737a87c..e0dec00e68 100644 --- a/core/chains/evm/config/config_test.go +++ b/core/chains/evm/config/config_test.go @@ -220,6 +220,13 @@ func TestChainScopedConfig_BlockHistory(t *testing.T) { assert.Equal(t, uint16(1), bh.BlockDelay()) assert.Equal(t, uint16(4), bh.EIP1559FeeCapBufferBlocks()) } +func TestChainScopedConfig_FeeHistory(t *testing.T) { + t.Parallel() + cfg := testutils.NewTestChainScopedConfig(t, nil) + + u := cfg.EVM().GasEstimator().FeeHistory() + assert.Equal(t, 10*time.Second, u.CacheTimeout()) +} func TestChainScopedConfig_GasEstimator(t *testing.T) { t.Parallel() diff --git a/core/chains/evm/config/mocks/gas_estimator.go b/core/chains/evm/config/mocks/gas_estimator.go index 96ffff08ae..70b9c18d0b 100644 --- a/core/chains/evm/config/mocks/gas_estimator.go +++ b/core/chains/evm/config/mocks/gas_estimator.go @@ -390,6 +390,53 @@ func (_c *GasEstimator_FeeCapDefault_Call) RunAndReturn(run func() *assets.Wei) return _c } +// FeeHistory provides a mock function with given fields: +func (_m *GasEstimator) FeeHistory() config.FeeHistory { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 config.FeeHistory + if rf, ok := ret.Get(0).(func() config.FeeHistory); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(config.FeeHistory) + } + } + + return r0 +} + +// GasEstimator_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' +type GasEstimator_FeeHistory_Call struct { + *mock.Call +} + +// FeeHistory is a helper method to define mock.On call +func (_e *GasEstimator_Expecter) FeeHistory() *GasEstimator_FeeHistory_Call { + return &GasEstimator_FeeHistory_Call{Call: _e.mock.On("FeeHistory")} +} + +func (_c *GasEstimator_FeeHistory_Call) Run(run func()) *GasEstimator_FeeHistory_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *GasEstimator_FeeHistory_Call) Return(_a0 config.FeeHistory) *GasEstimator_FeeHistory_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *GasEstimator_FeeHistory_Call) RunAndReturn(run func() config.FeeHistory) *GasEstimator_FeeHistory_Call { + _c.Call.Return(run) + return _c +} + // LimitDefault provides a mock function with given fields: func (_m *GasEstimator) LimitDefault() uint64 { ret := _m.Called() diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index 0e823ffdda..90854d90cf 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -568,6 +568,7 @@ type GasEstimator struct { TipCapMin *assets.Wei BlockHistory BlockHistoryEstimator `toml:",omitempty"` + FeeHistory FeeHistoryEstimator `toml:",omitempty"` } func (e *GasEstimator) ValidateConfig() (err error) { @@ -662,6 +663,7 @@ func (e *GasEstimator) setFrom(f *GasEstimator) { } e.LimitJobType.setFrom(&f.LimitJobType) e.BlockHistory.setFrom(&f.BlockHistory) + e.FeeHistory.setFrom(&f.FeeHistory) } type GasLimitJobType struct { @@ -724,6 +726,16 @@ func (e *BlockHistoryEstimator) setFrom(f *BlockHistoryEstimator) { } } +type FeeHistoryEstimator struct { + CacheTimeout *commonconfig.Duration +} + +func (u *FeeHistoryEstimator) setFrom(f *FeeHistoryEstimator) { + if v := f.CacheTimeout; v != nil { + u.CacheTimeout = v + } +} + type KeySpecificConfig []KeySpecific func (ks KeySpecificConfig) ValidateConfig() (err error) { diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index be3b19677d..fb8eed3949 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -57,6 +57,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/core/chains/evm/gas/fee_history_estimator.go b/core/chains/evm/gas/fee_history_estimator.go new file mode 100644 index 0000000000..ba3192be10 --- /dev/null +++ b/core/chains/evm/gas/fee_history_estimator.go @@ -0,0 +1,440 @@ +package gas + +import ( + "context" + "fmt" + "math/big" + "strconv" + "sync" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" + + commonfee "github.com/smartcontractkit/chainlink/v2/common/fee" + feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/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/rollups" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +// metrics are thread safe +var ( + promFeeHistoryEstimatorGasPrice = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "gas_price_updater", + Help: "Sets latest gas price (in Wei)", + }, + []string{"evmChainID"}, + ) + promFeeHistoryEstimatorBaseFee = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "base_fee_updater", + Help: "Sets latest BaseFee (in Wei)", + }, + []string{"evmChainID"}, + ) + promFeeHistoryEstimatorMaxPriorityFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "max_priority_fee_per_gas_updater", + Help: "Sets latest MaxPriorityFeePerGas (in Wei)", + }, + []string{"evmChainID"}, + ) + promFeeHistoryEstimatorMaxFeePerGas = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "max_fee_per_gas_updater", + Help: "Sets latest MaxFeePerGas (in Wei)", + }, + []string{"evmChainID"}, + ) +) + +const ( + MinimumBumpPercentage = 10 // based on geth's spec + ConnectivityPercentile = 85 + BaseFeeBufferPercentage = 40 +) + +type FeeHistoryEstimatorConfig struct { + BumpPercent uint16 + CacheTimeout time.Duration + + EIP1559 bool + BlockHistorySize uint64 + RewardPercentile float64 +} + +type feeHistoryEstimatorClient interface { + SuggestGasPrice(ctx context.Context) (*big.Int, error) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) +} + +type FeeHistoryEstimator struct { + services.StateMachine + + client feeHistoryEstimatorClient + logger logger.Logger + config FeeHistoryEstimatorConfig + chainID *big.Int + + gasPriceMu sync.RWMutex + gasPrice *assets.Wei + + dynamicPriceMu sync.RWMutex + dynamicPrice DynamicFee + + priorityFeeThresholdMu sync.RWMutex + priorityFeeThreshold *assets.Wei + + l1Oracle rollups.L1Oracle + + wg *sync.WaitGroup + stopCh services.StopChan + refreshCh chan struct{} +} + +func NewFeeHistoryEstimator(lggr logger.Logger, client feeHistoryEstimatorClient, cfg FeeHistoryEstimatorConfig, chainID *big.Int, l1Oracle rollups.L1Oracle) *FeeHistoryEstimator { + return &FeeHistoryEstimator{ + client: client, + logger: logger.Named(lggr, "FeeHistoryEstimator"), + config: cfg, + chainID: chainID, + l1Oracle: l1Oracle, + wg: new(sync.WaitGroup), + stopCh: make(chan struct{}), + refreshCh: make(chan struct{}), + } +} + +func (f *FeeHistoryEstimator) Start(context.Context) error { + return f.StartOnce("FeeHistoryEstimator", func() error { + if f.config.BumpPercent < MinimumBumpPercentage { + return fmt.Errorf("BumpPercent: %s is less than minimum allowed percentage: %s", + strconv.FormatUint(uint64(f.config.BumpPercent), 10), strconv.Itoa(MinimumBumpPercentage)) + } + if f.config.EIP1559 && f.config.RewardPercentile > ConnectivityPercentile { + return fmt.Errorf("RewardPercentile: %s is greater than maximum allowed percentile: %s", + strconv.FormatUint(uint64(f.config.RewardPercentile), 10), strconv.Itoa(ConnectivityPercentile)) + } + f.wg.Add(1) + go f.run() + + return nil + }) +} + +func (f *FeeHistoryEstimator) Close() error { + return f.StopOnce("FeeHistoryEstimator", func() error { + close(f.stopCh) + f.wg.Wait() + return nil + }) +} + +func (f *FeeHistoryEstimator) run() { + defer f.wg.Done() + + t := services.TickerConfig{ + JitterPct: services.DefaultJitter, + }.NewTicker(f.config.CacheTimeout) + + for { + select { + case <-f.stopCh: + return + case <-f.refreshCh: + t.Reset() + case <-t.C: + if f.config.EIP1559 { + if err := f.RefreshDynamicPrice(); err != nil { + f.logger.Error(err) + } + } else { + if _, err := f.RefreshGasPrice(); err != nil { + f.logger.Error(err) + } + } + } + } +} + +// GetLegacyGas will fetch the cached gas price value. +func (f *FeeHistoryEstimator) GetLegacyGas(ctx context.Context, _ []byte, gasLimit uint64, maxPrice *assets.Wei, opts ...feetypes.Opt) (gasPrice *assets.Wei, chainSpecificGasLimit uint64, err error) { + chainSpecificGasLimit = gasLimit + if gasPrice, err = f.getGasPrice(); err != nil { + return + } + + if gasPrice.Cmp(maxPrice) > 0 { + f.logger.Warnf("estimated gas price: %s is greater than the maximum gas price configured: %s, returning the maximum price instead.", gasPrice, maxPrice) + return maxPrice, chainSpecificGasLimit, nil + } + return +} + +// RefreshGasPrice will use eth_gasPrice to fetch and cache the latest gas price from the RPC. +func (f *FeeHistoryEstimator) RefreshGasPrice() (*assets.Wei, error) { + ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) + defer cancel() + + gasPrice, err := f.client.SuggestGasPrice(ctx) + if err != nil { + return nil, err + } + + promFeeHistoryEstimatorGasPrice.WithLabelValues(f.chainID.String()).Set(float64(gasPrice.Int64())) + + gasPriceWei := assets.NewWei(gasPrice) + + f.logger.Debugf("fetched new gas price: %v", gasPriceWei) + + f.gasPriceMu.Lock() + defer f.gasPriceMu.Unlock() + f.gasPrice = gasPriceWei + return f.gasPrice, nil +} + +func (f *FeeHistoryEstimator) getGasPrice() (*assets.Wei, error) { + f.gasPriceMu.RLock() + defer f.gasPriceMu.RUnlock() + if f.gasPrice == nil { + return f.gasPrice, fmt.Errorf("gas price not set") + } + return f.gasPrice, nil +} + +// GetDynamicFee will fetch the cached dynamic prices. +func (f *FeeHistoryEstimator) GetDynamicFee(ctx context.Context, maxPrice *assets.Wei) (fee DynamicFee, err error) { + if fee, err = f.getDynamicPrice(); err != nil { + return + } + + if fee.FeeCap.Cmp(maxPrice) > 0 { + f.logger.Warnf("estimated maxFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", + fee.FeeCap, maxPrice) + fee.FeeCap = maxPrice + if fee.TipCap.Cmp(maxPrice) > 0 { + f.logger.Warnf("estimated maxPriorityFeePerGas: %v is greater than the maximum price configured: %v, returning the maximum price instead.", + fee.TipCap, maxPrice) + fee.TipCap = maxPrice + } + } + + return +} + +// RefreshDynamicPrice uses eth_feeHistory to fetch the baseFee of the next block and the Nth maxPriorityFeePerGas percentiles +// of the past X blocks. It also fetches the highest 85th maxPriorityFeePerGas percentile of the past X blocks, which represents +// the highest percentile we're willing to pay. A buffer is added on top of the latest baseFee to catch fluctuations in the next +// blocks. On Ethereum the increase is baseFee * 1.125 per block, however in some chains that may vary. +func (f *FeeHistoryEstimator) RefreshDynamicPrice() error { + ctx, cancel := f.stopCh.CtxCancel(evmclient.ContextWithDefaultTimeout()) + defer cancel() + + // RewardPercentile will be used for maxPriorityFeePerGas estimations and connectivityPercentile to set the highest threshold for bumping. + feeHistory, err := f.client.FeeHistory(ctx, max(f.config.BlockHistorySize, 1), []float64{f.config.RewardPercentile, ConnectivityPercentile}) + if err != nil { + return err + } + + // eth_feeHistory doesn't return the latest baseFee of the range but rather the latest + 1, because it can be derived from the existing + // values. Source: https://github.com/ethereum/go-ethereum/blob/b0f66e34ca2a4ea7ae23475224451c8c9a569826/eth/gasprice/feehistory.go#L235 + // nextBlock is the latest returned + 1 to be aligned with the base fee value. + nextBaseFee := assets.NewWei(feeHistory.BaseFee[len(feeHistory.BaseFee)-1]) + nextBlock := big.NewInt(0).Add(feeHistory.OldestBlock, big.NewInt(int64(f.config.BlockHistorySize))) + + // If BlockHistorySize is 0 it means priority fees will be ignored from the calculations, so we set them to 0. + // If it's not we exclude 0 priced priority fees from the RPC response, even though some networks allow them. For empty blocks, eth_feeHistory + // returns priority fees with 0 values so it's safer to discard them in order to pick values from a more representative sample. + maxPriorityFeePerGas := assets.NewWeiI(0) + priorityFeeThresholdWei := assets.NewWeiI(0) + if f.config.BlockHistorySize > 0 { + var nonZeroRewardsLen int64 = 0 + priorityFee := big.NewInt(0) + priorityFeeThreshold := big.NewInt(0) + for _, reward := range feeHistory.Reward { + // reward needs to have values for two percentiles + if len(reward) < 2 { + return fmt.Errorf("reward size incorrect: %d", len(reward)) + } + // We'll calculate the average of non-zero priority fees + if reward[0].Cmp(big.NewInt(0)) > 0 { + priorityFee = priorityFee.Add(priorityFee, reward[0]) + nonZeroRewardsLen += 1 + } + // We take the max value for the bumping threshold + if reward[1].Cmp(big.NewInt(0)) > 0 { + priorityFeeThreshold = bigmath.Max(priorityFeeThreshold, reward[1]) + } + } + + if nonZeroRewardsLen == 0 || priorityFeeThreshold.Cmp(big.NewInt(0)) == 0 { + return nil + } + priorityFeeThresholdWei = assets.NewWei(priorityFeeThreshold) + maxPriorityFeePerGas = assets.NewWei(priorityFee.Div(priorityFee, big.NewInt(nonZeroRewardsLen))) + } + // baseFeeBufferPercentage is added on top as a safety to catch fluctuations in the next blocks. + maxFeePerGas := nextBaseFee.AddPercentage(BaseFeeBufferPercentage).Add(maxPriorityFeePerGas) + + promFeeHistoryEstimatorBaseFee.WithLabelValues(f.chainID.String()).Set(float64(nextBaseFee.Int64())) + promFeeHistoryEstimatorMaxPriorityFeePerGas.WithLabelValues(f.chainID.String()).Set(float64(maxPriorityFeePerGas.Int64())) + promFeeHistoryEstimatorMaxFeePerGas.WithLabelValues(f.chainID.String()).Set(float64(maxFeePerGas.Int64())) + + f.logger.Debugf("Fetched new dynamic prices, nextBlock#: %v - oldestBlock#: %v - nextBaseFee: %v - maxFeePerGas: %v - maxPriorityFeePerGas: %v - maxPriorityFeeThreshold: %v", + nextBlock, feeHistory.OldestBlock, nextBaseFee, maxFeePerGas, maxPriorityFeePerGas, priorityFeeThresholdWei) + + f.priorityFeeThresholdMu.Lock() + f.priorityFeeThreshold = priorityFeeThresholdWei + f.priorityFeeThresholdMu.Unlock() + + f.dynamicPriceMu.Lock() + defer f.dynamicPriceMu.Unlock() + f.dynamicPrice.FeeCap = maxFeePerGas + f.dynamicPrice.TipCap = maxPriorityFeePerGas + return nil +} + +func (f *FeeHistoryEstimator) getDynamicPrice() (fee DynamicFee, err error) { + f.dynamicPriceMu.RLock() + defer f.dynamicPriceMu.RUnlock() + if f.dynamicPrice.FeeCap == nil || f.dynamicPrice.TipCap == nil { + return fee, fmt.Errorf("dynamic price not set") + } + return f.dynamicPrice, nil +} + +// BumpLegacyGas provides a bumped gas price value by bumping the previous one by BumpPercent. +// If the original value is higher than the max price it returns an error as there is no room for bumping. +// It aggregates the market, bumped, and max gas price to provide a correct value. +func (f *FeeHistoryEstimator) BumpLegacyGas(ctx context.Context, originalGasPrice *assets.Wei, gasLimit uint64, maxPrice *assets.Wei, _ []EvmPriorAttempt) (*assets.Wei, uint64, error) { + // Sanitize original fee input + if originalGasPrice == nil || originalGasPrice.Cmp(maxPrice) >= 0 { + return nil, 0, fmt.Errorf("%w: error while retrieving original gas price: originalGasPrice: %s. Maximum price configured: %s", + commonfee.ErrBump, originalGasPrice, maxPrice) + } + + currentGasPrice, err := f.RefreshGasPrice() + if err != nil { + return nil, 0, err + } + f.IfStarted(func() { f.refreshCh <- struct{}{} }) + + bumpedGasPrice := originalGasPrice.AddPercentage(f.config.BumpPercent) + bumpedGasPrice, err = LimitBumpedFee(originalGasPrice, currentGasPrice, bumpedGasPrice, maxPrice) + if err != nil { + return nil, 0, fmt.Errorf("failed to limit gas price: %w", err) + } + + f.logger.Debugw("bumped gas price", "originalGasPrice", originalGasPrice, "marketGasPrice", currentGasPrice, "bumpedGasPrice", bumpedGasPrice) + + return bumpedGasPrice, gasLimit, nil +} + +// BumpDynamicFee provides a bumped dynamic fee by bumping the previous one by BumpPercent. +// If the original values are higher than the max price it returns an error as there is no room for bumping. If maxPriorityFeePerGas is bumped +// above the priority fee threshold then there is a good chance there is a connectivity issue and we shouldn't bump. +// Both maxFeePerGas as well as maxPriorityFeePerGas need to be bumped otherwise the RPC won't accept the transaction and throw an error. +// See: https://github.com/ethereum/go-ethereum/issues/24284 +// It aggregates the market, bumped, and max price to provide a correct value, for both maxFeePerGas as well as maxPriorityFerPergas. +func (f *FeeHistoryEstimator) BumpDynamicFee(ctx context.Context, originalFee DynamicFee, maxPrice *assets.Wei, _ []EvmPriorAttempt) (bumped DynamicFee, err error) { + // For chains that don't have a mempool there is no concept of gas bumping so we force-call RefreshDynamicPrice to update the underlying base fee value + if f.config.BlockHistorySize == 0 { + if !f.IfStarted(func() { + if refreshErr := f.RefreshDynamicPrice(); refreshErr != nil { + err = refreshErr + return + } + f.refreshCh <- struct{}{} + bumped, err = f.GetDynamicFee(ctx, maxPrice) + }) { + return bumped, fmt.Errorf("estimator not started") + } + return bumped, err + } + + // Sanitize original fee input + // According to geth's spec we need to bump both maxFeePerGas and maxPriorityFeePerGas for the new attempt to be accepted by the RPC + if originalFee.FeeCap == nil || + originalFee.TipCap == nil || + ((originalFee.TipCap.Cmp(originalFee.FeeCap)) > 0) || + (originalFee.FeeCap.Cmp(maxPrice) >= 0) { + return bumped, fmt.Errorf("%w: error while retrieving original dynamic fees: (originalFeePerGas: %s - originalPriorityFeePerGas: %s). Maximum price configured: %s", + commonfee.ErrBump, originalFee.FeeCap, originalFee.TipCap, maxPrice) + } + + currentDynamicPrice, err := f.getDynamicPrice() + if err != nil { + return + } + + bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(f.config.BumpPercent) + bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(f.config.BumpPercent) + + bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) + if err != nil { + return bumped, fmt.Errorf("failed to limit maxPriorityFeePerGas: %w", err) + } + + priorityFeeThreshold, e := f.getPriorityFeeThreshold() + if e != nil { + return bumped, e + } + + if bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { + return bumped, fmt.Errorf("bumpedMaxPriorityFeePerGas: %s is above market's %sth percentile: %s, bumping is halted", + bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) + } + + bumpedMaxFeePerGas, err = LimitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice) + if err != nil { + return bumped, fmt.Errorf("failed to limit maxFeePerGas: %w", err) + } + + bumpedFee := DynamicFee{FeeCap: bumpedMaxFeePerGas, TipCap: bumpedMaxPriorityFeePerGas} + f.logger.Debugw("bumped dynamic fee", "originalFee", originalFee, "marketFee", currentDynamicPrice, "bumpedFee", bumpedFee) + + return bumpedFee, nil +} + +// LimitBumpedFee selects the maximum value between the bumped attempt and the current fee, if there is one. If the result is higher than the max price it gets capped. +// Geth's implementation has a hard 10% minimum limit for the bumped values, otherwise it rejects the transaction with an error. +// See: https://github.com/ethereum/go-ethereum/blob/bff330335b94af3643ac2fb809793f77de3069d4/core/tx_list.go#L298 +// +// Note: for chains that support EIP-1559 but we still choose to send Legacy transactions to them, the limit is still enforcable due to the fact that Legacy transactions +// are treated the same way as Dynamic transactions under the hood. For chains that don't support EIP-1559 at all, the limit isn't enforcable but a 10% minimum bump percentage +// makes sense anyway. +func LimitBumpedFee(originalFee *assets.Wei, currentFee *assets.Wei, bumpedFee *assets.Wei, maxPrice *assets.Wei) (*assets.Wei, error) { + if currentFee != nil { + bumpedFee = assets.WeiMax(currentFee, bumpedFee) + } + bumpedFee = assets.WeiMin(bumpedFee, maxPrice) + + // The first check is added for the following edge case: + // If originalFee is below 10 wei, then adding the minimum bump percentage won't have any effect on the final value because of rounding down. + // Similarly for bumpedFee, it can have the exact same value as the originalFee, even if we bumped, given an originalFee of less than 10 wei + // and a small enough BumpPercent. + if bumpedFee.Cmp(originalFee) == 0 || + bumpedFee.Cmp(originalFee.AddPercentage(MinimumBumpPercentage)) < 0 { + return nil, fmt.Errorf("%w: %s is bumped less than minimum allowed percentage(%s) from originalFee: %s - maxPrice: %s", + commonfee.ErrBump, bumpedFee, strconv.Itoa(MinimumBumpPercentage), originalFee, maxPrice) + } + return bumpedFee, nil +} + +func (f *FeeHistoryEstimator) getPriorityFeeThreshold() (*assets.Wei, error) { + f.priorityFeeThresholdMu.RLock() + defer f.priorityFeeThresholdMu.RUnlock() + if f.priorityFeeThreshold == nil { + return f.priorityFeeThreshold, fmt.Errorf("priorityFeeThreshold not set") + } + return f.priorityFeeThreshold, nil +} + +func (f *FeeHistoryEstimator) Name() string { return f.logger.Name() } +func (f *FeeHistoryEstimator) L1Oracle() rollups.L1Oracle { return f.l1Oracle } +func (f *FeeHistoryEstimator) HealthReport() map[string]error { return map[string]error{f.Name(): nil} } +func (f *FeeHistoryEstimator) OnNewLongestChain(context.Context, *evmtypes.Head) {} diff --git a/core/chains/evm/gas/fee_history_estimator_test.go b/core/chains/evm/gas/fee_history_estimator_test.go new file mode 100644 index 0000000000..6e42e0e209 --- /dev/null +++ b/core/chains/evm/gas/fee_history_estimator_test.go @@ -0,0 +1,513 @@ +package gas_test + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" +) + +func TestFeeHistoryEstimatorLifecycle(t *testing.T) { + t.Parallel() + var gasLimit uint64 = 21000 + maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) + + t.Run("fails if you fetch gas price before the estimator starts", func(t *testing.T) { + cfg := gas.FeeHistoryEstimatorConfig{ + BumpPercent: 20, + RewardPercentile: 60, + EIP1559: false, + } + + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) + _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.ErrorContains(t, err, "gas price not set") + }) + + t.Run("fails to start if BumpPercent is lower than the minimum cap", func(t *testing.T) { + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 9} + + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) + assert.ErrorContains(t, u.Start(tests.Context(t)), "BumpPercent") + }) + + t.Run("fails to start if RewardPercentile is higher than ConnectivityPercentile in EIP-1559", func(t *testing.T) { + cfg := gas.FeeHistoryEstimatorConfig{ + BumpPercent: 20, + RewardPercentile: 99, + EIP1559: true, + } + + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) + assert.ErrorContains(t, u.Start(tests.Context(t)), "RewardPercentile") + }) + + t.Run("starts if configs are correct", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Maybe() + + cfg := gas.FeeHistoryEstimatorConfig{ + BumpPercent: 20, + RewardPercentile: 10, + CacheTimeout: 10 * time.Second, + } + + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) + err := u.Start(tests.Context(t)) + assert.NoError(t, err) + err = u.Close() + assert.NoError(t, err) + }) +} + +func TestFeeHistoryEstimatorGetLegacyGas(t *testing.T) { + t.Parallel() + + var gasLimit uint64 = 21000 + maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) + + t.Run("fetches a new gas price when first called", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + + cfg := gas.FeeHistoryEstimatorConfig{} + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.RefreshGasPrice() + assert.NoError(t, err) + gasPrice, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(10), gasPrice) + }) + + t.Run("will return max price if estimation exceeds it", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil).Once() + + cfg := gas.FeeHistoryEstimatorConfig{} + + maxPrice := assets.NewWeiI(1) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + _, err := u.RefreshGasPrice() + assert.NoError(t, err) + gas1, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxPrice, gas1) + }) + + t.Run("fails if gas price has not been set yet", func(t *testing.T) { + cfg := gas.FeeHistoryEstimatorConfig{} + + maxPrice := assets.NewWeiI(1) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) + _, _, err := u.GetLegacyGas(tests.Context(t), nil, gasLimit, maxPrice) + assert.Error(t, err) + assert.ErrorContains(t, err, "gas price not set") + }) +} + +func TestFeeHistoryEstimatorBumpLegacyGas(t *testing.T) { + t.Parallel() + + var gasLimit uint64 = 21000 + maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) + + t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + originalGasPrice := assets.NewWeiI(10) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(10), nil) + + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50, CacheTimeout: 5 * time.Second} + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + servicetest.RunHealthy(t, u) + gasPrice, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(15), gasPrice) + }) + + t.Run("fails if the original attempt is nil, or equal or higher than the max price", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + + cfg := gas.FeeHistoryEstimatorConfig{} + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + + var originalPrice *assets.Wei + _, _, err := u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil) + assert.Error(t, err) + + originalPrice = assets.NewWeiI(100) + _, _, err = u.BumpLegacyGas(tests.Context(t), originalPrice, gasLimit, maxPrice, nil) + assert.Error(t, err) + }) + + t.Run("returns market gas price if bumped original fee is lower", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(80), nil).Once() + originalGasPrice := assets.NewWeiI(10) + + cfg := gas.FeeHistoryEstimatorConfig{} + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(80), gas) + }) + + t.Run("returns max gas price if bumped original fee is higher", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(1), nil).Once() + originalGasPrice := assets.NewWeiI(10) + + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50} + + maxPrice := assets.NewWeiI(14) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, maxPrice, gas) + }) + + t.Run("returns max gas price if the aggregation of max and original bumped fee is higher", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(1), nil).Once() + originalGasPrice := assets.NewWeiI(10) + + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 50} + + maxPrice := assets.NewWeiI(14) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + gas, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, maxPrice, gas) + }) + + t.Run("fails if the bumped gas price is lower than the minimum bump percentage", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + client.On("SuggestGasPrice", mock.Anything).Return(big.NewInt(100), nil).Once() + originalGasPrice := assets.NewWeiI(100) + + cfg := gas.FeeHistoryEstimatorConfig{BumpPercent: 20} + + // Price will be capped by the max price + maxPrice := assets.NewWeiI(101) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + _, _, err := u.BumpLegacyGas(tests.Context(t), originalGasPrice, gasLimit, maxPrice, nil) + assert.Error(t, err) + }) +} + +func TestFeeHistoryEstimatorGetDynamicFee(t *testing.T) { + t.Parallel() + + maxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) + + t.Run("fetches a new dynamic fee when first called", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + baseFee := big.NewInt(5) + maxPriorityFeePerGas1 := big.NewInt(33) + maxPriorityFeePerGas2 := big.NewInt(20) + + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas1, big.NewInt(5)}, {maxPriorityFeePerGas2, big.NewInt(5)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee, baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + blockHistoryLength := 2 + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: uint64(blockHistoryLength)} + avrgPriorityFee := big.NewInt(0) + avrgPriorityFee.Add(maxPriorityFeePerGas1, maxPriorityFeePerGas2).Div(avrgPriorityFee, big.NewInt(int64(blockHistoryLength))) + maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(avrgPriorityFee)) + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + err := u.RefreshDynamicPrice() + assert.NoError(t, err) + dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxFee, dynamicFee.FeeCap) + assert.Equal(t, (*assets.Wei)(avrgPriorityFee), dynamicFee.TipCap) + }) + + t.Run("fails if dynamic prices have not been set yet", func(t *testing.T) { + cfg := gas.FeeHistoryEstimatorConfig{} + + maxPrice := assets.NewWeiI(1) + u := gas.NewFeeHistoryEstimator(logger.Test(t), nil, cfg, chainID, nil) + _, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.Error(t, err) + assert.ErrorContains(t, err, "dynamic price not set") + }) + + t.Run("will return max price if tip cap or fee cap exceed it", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + baseFee := big.NewInt(1) + maxPriorityFeePerGas := big.NewInt(3) + maxPrice := assets.NewWeiI(2) + + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(5)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + err := u.RefreshDynamicPrice() + assert.NoError(t, err) + dynamicFee, err := u.GetDynamicFee(tests.Context(t), maxPrice) + assert.NoError(t, err) + assert.Equal(t, maxPrice, dynamicFee.FeeCap) + assert.Equal(t, maxPrice, dynamicFee.TipCap) + }) +} + +func TestFeeHistoryEstimatorBumpDynamicFee(t *testing.T) { + t.Parallel() + + globalMaxPrice := assets.NewWeiI(100) + chainID := big.NewInt(0) + + t.Run("bumps a previous attempt by BumpPercent", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(10), + } + + // These values will be ignored because they are lower prices than the originalFee + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{big.NewInt(5), big.NewInt(50)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{big.NewInt(5)}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.FeeHistoryEstimatorConfig{ + BlockHistorySize: 2, + BumpPercent: 50, + } + + expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent) + expectedTipCap := originalFee.TipCap.AddPercentage(cfg.BumpPercent) + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + err := u.RefreshDynamicPrice() + assert.NoError(t, err) + dynamicFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, expectedFeeCap, dynamicFee.FeeCap) + assert.Equal(t, expectedTipCap, dynamicFee.TipCap) + }) + + t.Run("fails if the original attempt is invalid", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + maxPrice := assets.NewWeiI(20) + cfg := gas.FeeHistoryEstimatorConfig{BlockHistorySize: 1} + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + // nil original fee + var originalFee gas.DynamicFee + _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + + // tip cap is higher than fee cap + originalFee = gas.DynamicFee{ + FeeCap: assets.NewWeiI(10), + TipCap: assets.NewWeiI(11), + } + _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + + // fee cap is equal or higher to max price + originalFee = gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(10), + } + _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + }) + + t.Run("returns market prices if bumped original fee is lower", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(10), + } + + // Market fees + baseFee := big.NewInt(5) + maxPriorityFeePerGas := big.NewInt(33) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(100)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + maxFee := (*assets.Wei)(baseFee).AddPercentage(gas.BaseFeeBufferPercentage).Add((*assets.Wei)(maxPriorityFeePerGas)) + + cfg := gas.FeeHistoryEstimatorConfig{ + BlockHistorySize: 1, + BumpPercent: 50, + } + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + err := u.RefreshDynamicPrice() + assert.NoError(t, err) + bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, (*assets.Wei)(maxPriorityFeePerGas), bumpedFee.TipCap) + assert.Equal(t, maxFee, bumpedFee.FeeCap) + }) + + t.Run("fails if connectivity percentile value is reached", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(10), + } + + // Market fees + baseFee := big.NewInt(5) + maxPriorityFeePerGas := big.NewInt(33) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.FeeHistoryEstimatorConfig{ + BlockHistorySize: 1, + BumpPercent: 50, + } + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + err := u.RefreshDynamicPrice() + assert.NoError(t, err) + _, err = u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) + assert.Error(t, err) + }) + + t.Run("returns max price if the aggregation of max and original bumped fee is higher", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(18), + } + + maxPrice := assets.NewWeiI(25) + // Market fees + baseFee := big.NewInt(1) + maxPriorityFeePerGas := big.NewInt(1) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.FeeHistoryEstimatorConfig{ + BlockHistorySize: 1, + BumpPercent: 50, + } + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + err := u.RefreshDynamicPrice() + assert.NoError(t, err) + bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, maxPrice, bumpedFee.TipCap) + assert.Equal(t, maxPrice, bumpedFee.FeeCap) + }) + + t.Run("fails if the bumped gas price is lower than the minimum bump percentage", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(20), + TipCap: assets.NewWeiI(18), + } + + maxPrice := assets.NewWeiI(21) + // Market fees + baseFee := big.NewInt(1) + maxPriorityFeePerGas := big.NewInt(1) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(30)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.FeeHistoryEstimatorConfig{ + BlockHistorySize: 1, + BumpPercent: 50, + } + + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + err := u.RefreshDynamicPrice() + assert.NoError(t, err) + _, err = u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) + assert.Error(t, err) + }) + + t.Run("ignores maxPriorityFeePerGas if there is no mempool and forces refetch", func(t *testing.T) { + client := mocks.NewFeeHistoryEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(40), + TipCap: assets.NewWeiI(0), + } + + // Market fees + baseFee := big.NewInt(10) + maxPriorityFeePerGas := big.NewInt(0) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(0)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil) + + cfg := gas.FeeHistoryEstimatorConfig{ + BlockHistorySize: 0, + BumpPercent: 20, + CacheTimeout: 10 * time.Second, + EIP1559: true, + } + + maxFeePerGas := assets.NewWei(baseFee).AddPercentage(gas.BaseFeeBufferPercentage) + u := gas.NewFeeHistoryEstimator(logger.Test(t), client, cfg, chainID, nil) + servicetest.RunHealthy(t, u) + bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(0), (*assets.Wei)(maxPriorityFeePerGas)) + assert.Equal(t, maxFeePerGas, bumpedFee.FeeCap) + }) +} diff --git a/core/chains/evm/gas/mocks/fee_estimator_client.go b/core/chains/evm/gas/mocks/fee_estimator_client.go index ab99eb7b0d..f5ca52ec92 100644 --- a/core/chains/evm/gas/mocks/fee_estimator_client.go +++ b/core/chains/evm/gas/mocks/fee_estimator_client.go @@ -298,6 +298,66 @@ func (_c *FeeEstimatorClient_EstimateGas_Call) RunAndReturn(run func(context.Con return _c } +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *FeeEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FeeEstimatorClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' +type FeeEstimatorClient_FeeHistory_Call struct { + *mock.Call +} + +// FeeHistory is a helper method to define mock.On call +// - ctx context.Context +// - blockCount uint64 +// - rewardPercentiles []float64 +func (_e *FeeEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *FeeEstimatorClient_FeeHistory_Call { + return &FeeEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +} + +func (_c *FeeEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *FeeEstimatorClient_FeeHistory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + }) + return _c +} + +func (_c *FeeEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *FeeEstimatorClient_FeeHistory_Call { + _c.Call.Return(feeHistory, err) + return _c +} + +func (_c *FeeEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *FeeEstimatorClient_FeeHistory_Call { + _c.Call.Return(run) + return _c +} + // HeadByNumber provides a mock function with given fields: ctx, n func (_m *FeeEstimatorClient) HeadByNumber(ctx context.Context, n *big.Int) (*types.Head, error) { ret := _m.Called(ctx, n) @@ -357,6 +417,64 @@ func (_c *FeeEstimatorClient_HeadByNumber_Call) RunAndReturn(run func(context.Co return _c } +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *FeeEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FeeEstimatorClient_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice' +type FeeEstimatorClient_SuggestGasPrice_Call struct { + *mock.Call +} + +// SuggestGasPrice is a helper method to define mock.On call +// - ctx context.Context +func (_e *FeeEstimatorClient_Expecter) SuggestGasPrice(ctx interface{}) *FeeEstimatorClient_SuggestGasPrice_Call { + return &FeeEstimatorClient_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)} +} + +func (_c *FeeEstimatorClient_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *FeeEstimatorClient_SuggestGasPrice_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *FeeEstimatorClient_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *FeeEstimatorClient_SuggestGasPrice_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *FeeEstimatorClient_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *FeeEstimatorClient_SuggestGasPrice_Call { + _c.Call.Return(run) + return _c +} + // NewFeeEstimatorClient creates a new instance of FeeEstimatorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFeeEstimatorClient(t interface { diff --git a/core/chains/evm/gas/mocks/fee_history_estimator_client.go b/core/chains/evm/gas/mocks/fee_history_estimator_client.go new file mode 100644 index 0000000000..7486214501 --- /dev/null +++ b/core/chains/evm/gas/mocks/fee_history_estimator_client.go @@ -0,0 +1,157 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + big "math/big" + + ethereum "github.com/ethereum/go-ethereum" + + mock "github.com/stretchr/testify/mock" +) + +// FeeHistoryEstimatorClient is an autogenerated mock type for the feeHistoryEstimatorClient type +type FeeHistoryEstimatorClient struct { + mock.Mock +} + +type FeeHistoryEstimatorClient_Expecter struct { + mock *mock.Mock +} + +func (_m *FeeHistoryEstimatorClient) EXPECT() *FeeHistoryEstimatorClient_Expecter { + return &FeeHistoryEstimatorClient_Expecter{mock: &_m.Mock} +} + +// FeeHistory provides a mock function with given fields: ctx, blockCount, rewardPercentiles +func (_m *FeeHistoryEstimatorClient) FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (*ethereum.FeeHistory, error) { + ret := _m.Called(ctx, blockCount, rewardPercentiles) + + if len(ret) == 0 { + panic("no return value specified for FeeHistory") + } + + var r0 *ethereum.FeeHistory + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)); ok { + return rf(ctx, blockCount, rewardPercentiles) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []float64) *ethereum.FeeHistory); ok { + r0 = rf(ctx, blockCount, rewardPercentiles) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ethereum.FeeHistory) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []float64) error); ok { + r1 = rf(ctx, blockCount, rewardPercentiles) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FeeHistoryEstimatorClient_FeeHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FeeHistory' +type FeeHistoryEstimatorClient_FeeHistory_Call struct { + *mock.Call +} + +// FeeHistory is a helper method to define mock.On call +// - ctx context.Context +// - blockCount uint64 +// - rewardPercentiles []float64 +func (_e *FeeHistoryEstimatorClient_Expecter) FeeHistory(ctx interface{}, blockCount interface{}, rewardPercentiles interface{}) *FeeHistoryEstimatorClient_FeeHistory_Call { + return &FeeHistoryEstimatorClient_FeeHistory_Call{Call: _e.mock.On("FeeHistory", ctx, blockCount, rewardPercentiles)} +} + +func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Run(run func(ctx context.Context, blockCount uint64, rewardPercentiles []float64)) *FeeHistoryEstimatorClient_FeeHistory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].([]float64)) + }) + return _c +} + +func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) Return(feeHistory *ethereum.FeeHistory, err error) *FeeHistoryEstimatorClient_FeeHistory_Call { + _c.Call.Return(feeHistory, err) + return _c +} + +func (_c *FeeHistoryEstimatorClient_FeeHistory_Call) RunAndReturn(run func(context.Context, uint64, []float64) (*ethereum.FeeHistory, error)) *FeeHistoryEstimatorClient_FeeHistory_Call { + _c.Call.Return(run) + return _c +} + +// SuggestGasPrice provides a mock function with given fields: ctx +func (_m *FeeHistoryEstimatorClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SuggestGasPrice") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FeeHistoryEstimatorClient_SuggestGasPrice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuggestGasPrice' +type FeeHistoryEstimatorClient_SuggestGasPrice_Call struct { + *mock.Call +} + +// SuggestGasPrice is a helper method to define mock.On call +// - ctx context.Context +func (_e *FeeHistoryEstimatorClient_Expecter) SuggestGasPrice(ctx interface{}) *FeeHistoryEstimatorClient_SuggestGasPrice_Call { + return &FeeHistoryEstimatorClient_SuggestGasPrice_Call{Call: _e.mock.On("SuggestGasPrice", ctx)} +} + +func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) Run(run func(ctx context.Context)) *FeeHistoryEstimatorClient_SuggestGasPrice_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) Return(_a0 *big.Int, _a1 error) *FeeHistoryEstimatorClient_SuggestGasPrice_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *FeeHistoryEstimatorClient_SuggestGasPrice_Call) RunAndReturn(run func(context.Context) (*big.Int, error)) *FeeHistoryEstimatorClient_SuggestGasPrice_Call { + _c.Call.Return(run) + return _c +} + +// NewFeeHistoryEstimatorClient creates a new instance of FeeHistoryEstimatorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewFeeHistoryEstimatorClient(t interface { + mock.TestingT + Cleanup(func()) +}) *FeeHistoryEstimatorClient { + mock := &FeeHistoryEstimatorClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index c1c2e60320..92ae439492 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -50,6 +50,8 @@ type feeEstimatorClient interface { ConfiguredChainID() *big.Int HeadByNumber(ctx context.Context, n *big.Int) (*evmtypes.Head, error) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) + SuggestGasPrice(ctx context.Context) (*big.Int, error) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) } // NewEstimator returns the estimator for a given config @@ -110,6 +112,18 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, cfg Config, newEstimator = func(l logger.Logger) EvmEstimator { return NewSuggestedPriceEstimator(lggr, ethClient, geCfg, l1Oracle) } + case "FeeHistory": + newEstimator = func(l logger.Logger) EvmEstimator { + ccfg := FeeHistoryEstimatorConfig{ + BumpPercent: geCfg.BumpPercent(), + CacheTimeout: geCfg.FeeHistory().CacheTimeout(), + EIP1559: geCfg.EIP1559DynamicFees(), + BlockHistorySize: uint64(geCfg.BlockHistory().BlockHistorySize()), + RewardPercentile: float64(geCfg.BlockHistory().TransactionPercentile()), + } + return NewFeeHistoryEstimator(lggr, ethClient, ccfg, ethClient.ConfiguredChainID(), l1Oracle) + } + default: lggr.Warnf("GasEstimator: unrecognised mode '%s', falling back to FixedPriceEstimator", s) newEstimator = func(l logger.Logger) EvmEstimator { diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index 963c608b53..b1317cb421 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -73,6 +73,10 @@ func (g *TestGasEstimatorConfig) BlockHistory() evmconfig.BlockHistory { return &TestBlockHistoryConfig{} } +func (g *TestGasEstimatorConfig) FeeHistory() evmconfig.FeeHistory { + return &TestFeeHistoryConfig{} +} + func (g *TestGasEstimatorConfig) EIP1559DynamicFees() bool { return false } func (g *TestGasEstimatorConfig) LimitDefault() uint64 { return 42 } func (g *TestGasEstimatorConfig) BumpPercent() uint16 { return 42 } @@ -121,6 +125,12 @@ func (b *TestBlockHistoryConfig) BlockHistorySize() uint16 { return 42 func (b *TestBlockHistoryConfig) EIP1559FeeCapBufferBlocks() uint16 { return 42 } func (b *TestBlockHistoryConfig) TransactionPercentile() uint16 { return 42 } +type TestFeeHistoryConfig struct { + evmconfig.FeeHistory +} + +func (b *TestFeeHistoryConfig) CacheTimeout() time.Duration { return 0 * time.Second } + type transactionsConfig struct { evmconfig.Transactions e *TestEvmConfig diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index 385432e12c..bffc9ddf0c 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -312,6 +312,15 @@ EIP1559FeeCapBufferBlocks = 13 # Example # Setting it lower will tend to set lower gas prices. TransactionPercentile = 60 # Default +[EVM.GasEstimator.FeeHistory] +# CacheTimeout is the time to wait in order to refresh the cached values stored in the FeeHistory estimator. A small jitter is applied so the timeout won't be exactly the same each time. +# +# You want this value to be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains you can skip a block or two +# and set it to two times the block time i.e. on Optimism you can set it to 4s. Ideally, you don't want to go lower than 1s since the RTT times of the RPC requests will be comparable to +# the timeout. The estimator is already adding a buffer to account for a potential increase in prices within one or two blocks. On the other hand, slower frequency will fail to refresh +# the prices and end up in stale values. +CacheTimeout = '10s' # Default + # The head tracker continually listens for new heads from the chain. # # In addition to these settings, it log warnings if `EVM.NoNewHeadsThreshold` is exceeded without any new blocks being emitted. diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 76150d2680..9afc0aa942 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -544,6 +544,9 @@ func TestConfig_Marshal(t *testing.T) { EIP1559FeeCapBufferBlocks: ptr[uint16](13), TransactionPercentile: ptr[uint16](15), }, + FeeHistory: evmcfg.FeeHistoryEstimator{ + CacheTimeout: &second, + }, }, KeySpecific: []evmcfg.KeySpecific{ @@ -1051,6 +1054,9 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '1s' + [EVM.HeadTracker] HistoryDepth = 15 MaxBufferSize = 17 diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index 08f688dfac..c10d59f339 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -342,6 +342,9 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '1s' + [EVM.HeadTracker] HistoryDepth = 15 MaxBufferSize = 17 diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index 4de06be86d..edb8d0a249 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -319,6 +319,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -420,6 +423,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -515,6 +521,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index d046a2ab8b..b546042756 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -341,6 +341,9 @@ CheckInclusionPercentile = 19 EIP1559FeeCapBufferBlocks = 13 TransactionPercentile = 15 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '1s' + [EVM.HeadTracker] HistoryDepth = 15 MaxBufferSize = 17 diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index df335e0474..ca9edca06f 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -319,6 +319,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -420,6 +423,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -515,6 +521,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 87e67701fc..6181a14bca 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1834,6 +1834,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -1929,6 +1932,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2024,6 +2030,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2119,6 +2128,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2215,6 +2227,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -2310,6 +2325,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2405,6 +2423,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2501,6 +2522,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2596,6 +2620,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2690,6 +2717,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2877,6 +2907,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -2972,6 +3005,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -3068,6 +3104,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -3163,6 +3202,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -3258,6 +3300,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -3353,6 +3398,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -3448,6 +3496,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -3543,6 +3594,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -3638,6 +3692,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 400 MaxBufferSize = 3 @@ -3733,6 +3790,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -3828,6 +3888,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 250 MaxBufferSize = 3 @@ -3923,6 +3986,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 1500 MaxBufferSize = 3 @@ -4019,6 +4085,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -4114,6 +4183,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4396,6 +4468,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4491,6 +4566,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4586,6 +4664,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -4681,6 +4762,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4776,6 +4860,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -4870,6 +4957,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 10 MaxBufferSize = 100 @@ -4965,6 +5055,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -5060,6 +5153,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 400 MaxBufferSize = 3 @@ -5155,6 +5251,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -5250,6 +5349,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5439,6 +5541,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5534,6 +5639,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -5629,6 +5737,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5725,6 +5836,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -5821,6 +5935,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6012,6 +6129,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6107,6 +6227,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -6202,6 +6325,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6297,6 +6423,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6392,6 +6521,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -6486,6 +6618,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6580,6 +6715,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 1000 MaxBufferSize = 3 @@ -6674,6 +6812,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 350 MaxBufferSize = 3 @@ -6769,6 +6910,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -6958,6 +7102,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -7052,6 +7199,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 2000 MaxBufferSize = 3 @@ -7242,6 +7392,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -7338,6 +7491,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -7434,6 +7590,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -7530,6 +7689,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -7626,6 +7788,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -7721,6 +7886,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -7816,6 +7984,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 50 MaxBufferSize = 3 @@ -7911,6 +8082,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -8006,6 +8180,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 300 MaxBufferSize = 3 @@ -8196,6 +8373,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -8291,6 +8471,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + [HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 @@ -8952,6 +9135,24 @@ Setting this number higher will cause the Chainlink node to select higher gas pr Setting it lower will tend to set lower gas prices. +## EVM.GasEstimator.FeeHistory +```toml +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' # Default +``` + + +### CacheTimeout +```toml +CacheTimeout = '10s' # Default +``` +CacheTimeout is the time to wait in order to refresh the cached values stored in the FeeHistory estimator. A small jitter is applied so the timeout won't be exactly the same each time. + +You want this value to be close to the block time. For slower chains, like Ethereum, you can set it to 12s, the same as the block time. For faster chains you can skip a block or two +and set it to two times the block time i.e. on Optimism you can set it to 4s. Ideally, you don't want to go lower than 1s since the RTT times of the RPC requests will be comparable to +the timeout. The estimator is already adding a buffer to account for a potential increase in prices within one or two blocks. On the other hand, slower frequency will fail to refresh +the prices and end up in stale values. + ## EVM.HeadTracker ```toml [EVM.HeadTracker] diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index fbcb4725d6..a6612cc8f1 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -375,6 +375,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar index 5fa31dd1aa..79dcfa776d 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -375,6 +375,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index 832185c53f..efa27eec11 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -375,6 +375,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 972c6c10fc..6932eb5038 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -365,6 +365,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index c7b5261d26..7074c82bf5 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -372,6 +372,9 @@ CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 50 +[EVM.GasEstimator.FeeHistory] +CacheTimeout = '10s' + [EVM.HeadTracker] HistoryDepth = 100 MaxBufferSize = 3 From 11bdcc3dd972b1f084081b411ad521bd3c6c8cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedemann=20F=C3=BCrst?= <59653747+friedemannf@users.noreply.github.com> Date: Thu, 5 Sep 2024 18:57:08 +0200 Subject: [PATCH 12/31] Add node level OOC error (#14315) * Add node level OOC error * Add changeset --- .changeset/green-eagles-deliver.md | 5 +++++ core/chains/evm/client/errors.go | 2 +- core/chains/evm/client/errors_test.go | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/green-eagles-deliver.md diff --git a/.changeset/green-eagles-deliver.md b/.changeset/green-eagles-deliver.md new file mode 100644 index 0000000000..179b93b108 --- /dev/null +++ b/.changeset/green-eagles-deliver.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Handle zkEVM node level OOC error as TerminallyStuck #internal diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index e7fff8d0db..91b32e7642 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -247,7 +247,7 @@ var zkSync = ClientErrors{ } var zkEvm = ClientErrors{ - TerminallyStuck: regexp.MustCompile(`(?:: |^)not enough .* counters to continue the execution$`), + TerminallyStuck: regexp.MustCompile(`(?:: |^)(?:not enough .* counters to continue the execution|out of counters at node level (?:.*))$`), } var aStar = ClientErrors{ diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index cee21c7ab4..f06cf8b135 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -317,6 +317,8 @@ func Test_Eth_Errors(t *testing.T) { {"failed to add tx to the pool: not enough step counters to continue the execution", true, "Xlayer"}, {"failed to add tx to the pool: not enough keccak counters to continue the execution", true, "zkEVM"}, {"failed to add tx to the pool: not enough keccak counters to continue the execution", true, "Xlayer"}, + {"RPC error response: failed to add tx to the pool: out of counters at node level (Steps)", true, "zkEVM"}, + {"RPC error response: failed to add tx to the pool: out of counters at node level (GasUsed, KeccakHashes, PoseidonHashes, PoseidonPaddings, MemAligns, Arithmetics, Binaries, Steps, Sha256Hashes)", true, "Xlayer"}, } for _, test := range tests { From 7725c8c72ddf3453e001d47ba60ffe72ebffd950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedemann=20F=C3=BCrst?= <59653747+friedemannf@users.noreply.github.com> Date: Tue, 24 Sep 2024 02:39:16 +0900 Subject: [PATCH 13/31] Change Polygon zkEVM to use FeeHistory estimator (#14161) * Change Polygon zkEVM to use SuggestedPriceEstimator (SHIP-2885) * Set PriceMin to 1mwei for Polygon zkEVM * Remove Polygon zkEVM Goerli * Enable FeeHistory estimator for Polygon zkEVM * Update PriceMin * apply suggestions * Set CacheTimeout to 2 seconds * Revert back to 5s cachetimeout * Change timeout to 4 seconds --------- Co-authored-by: joaoluisam --- .changeset/tender-lemons-obey.md | 5 ++++ .../toml/defaults/Polygon_Zkevm_Cardona.toml | 10 ++++---- .../toml/defaults/Polygon_Zkevm_Goerli.toml | 24 ------------------- .../toml/defaults/Polygon_Zkevm_Mainnet.toml | 10 ++++---- docs/CONFIG.md | 20 ++++++++-------- 5 files changed, 27 insertions(+), 42 deletions(-) create mode 100644 .changeset/tender-lemons-obey.md delete mode 100644 core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml diff --git a/.changeset/tender-lemons-obey.md b/.changeset/tender-lemons-obey.md new file mode 100644 index 0000000000..2d6cb774b0 --- /dev/null +++ b/.changeset/tender-lemons-obey.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Enable FeeHistory estimator for Polygon zkEVM #nops diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml index cd91465dae..46ce80e29f 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml @@ -13,12 +13,14 @@ ContractConfirmations = 1 ResendAfterThreshold = '3m' [GasEstimator] -PriceMin = '1 mwei' +Mode = 'FeeHistory' +# The FeeHistory estimator does not enforce PriceMin, setting it to 0 to not place any limits on the price +PriceMin = '0' BumpPercent = 40 -BumpMin = '20 mwei' -[GasEstimator.BlockHistory] -BlockHistorySize = 12 +[GasEstimator.FeeHistory] +# Refresh the suggested price every 4 seconds, to stay slightly below their polling rate of 5s +CacheTimeout = '4s' [HeadTracker] HistoryDepth = 2000 diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml deleted file mode 100644 index 6a9b47190f..0000000000 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml +++ /dev/null @@ -1,24 +0,0 @@ -ChainID = '1442' -ChainType = 'zkevm' -FinalityDepth = 500 -NoNewHeadsThreshold = '12m' -MinIncomingConfirmations = 1 -LogPollInterval = '30s' -RPCDefaultBatchSize = 100 - -[OCR] -ContractConfirmations = 1 - -[Transactions] -ResendAfterThreshold = '3m' - -[GasEstimator] -PriceMin = '50 mwei' -BumpPercent = 40 -BumpMin = '20 mwei' - -[GasEstimator.BlockHistory] -BlockHistorySize = 12 - -[HeadTracker] -HistoryDepth = 2000 diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml index 79e0cb0fce..2fef7874d1 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml @@ -14,12 +14,14 @@ ContractConfirmations = 1 ResendAfterThreshold = '3m' [GasEstimator] -PriceMin = '100 mwei' +Mode = 'FeeHistory' +# The FeeHistory estimator does not enforce PriceMin, setting it to 0 to not place any limits on the price +PriceMin = '0' BumpPercent = 40 -BumpMin = '100 mwei' -[GasEstimator.BlockHistory] -BlockHistorySize = 12 +[GasEstimator.FeeHistory] +# Refresh the suggested price every 4 seconds, to stay slightly below their polling rate of 5s +CacheTimeout = '4s' [HeadTracker] # Polygon suffers from a tremendous number of re-orgs, we need to set this to something very large to be conservative enough diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 6181a14bca..028b152868 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -4640,16 +4640,16 @@ Enabled = false Enabled = true [GasEstimator] -Mode = 'BlockHistory' +Mode = 'FeeHistory' PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '100 mwei' +PriceMin = '0' LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateLimit = false -BumpMin = '100 mwei' +BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 EIP1559DynamicFees = false @@ -4659,13 +4659,13 @@ TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 12 +BlockHistorySize = 8 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 [GasEstimator.FeeHistory] -CacheTimeout = '10s' +CacheTimeout = '4s' [HeadTracker] HistoryDepth = 2000 @@ -5227,16 +5227,16 @@ Enabled = false Enabled = true [GasEstimator] -Mode = 'BlockHistory' +Mode = 'FeeHistory' PriceDefault = '20 gwei' PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' -PriceMin = '1 mwei' +PriceMin = '0' LimitDefault = 8000000 LimitMax = 8000000 LimitMultiplier = '1' LimitTransfer = 21000 EstimateLimit = false -BumpMin = '20 mwei' +BumpMin = '5 gwei' BumpPercent = 40 BumpThreshold = 3 EIP1559DynamicFees = false @@ -5246,13 +5246,13 @@ TipCapMin = '1 wei' [GasEstimator.BlockHistory] BatchSize = 25 -BlockHistorySize = 12 +BlockHistorySize = 8 CheckInclusionBlocks = 12 CheckInclusionPercentile = 90 TransactionPercentile = 60 [GasEstimator.FeeHistory] -CacheTimeout = '10s' +CacheTimeout = '4s' [HeadTracker] HistoryDepth = 2000 From 00c81f8c52572c635315cfb1903def2b7b66b09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedemann=20F=C3=BCrst?= <59653747+friedemannf@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:47:38 +0200 Subject: [PATCH 14/31] Add Zircuit Configs (#14541) * Add Zircuit Configs * Add Changeset * Update changeset --- .changeset/khaki-tigers-agree.md | 5 + .../config/toml/defaults/Zircuit_Mainnet.toml | 36 +++ .../config/toml/defaults/Zircuit_Sepolia.toml | 36 +++ docs/CONFIG.md | 212 ++++++++++++++++++ 4 files changed, 289 insertions(+) create mode 100644 .changeset/khaki-tigers-agree.md create mode 100644 core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml create mode 100644 core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml diff --git a/.changeset/khaki-tigers-agree.md b/.changeset/khaki-tigers-agree.md new file mode 100644 index 0000000000..ba9e56cc1d --- /dev/null +++ b/.changeset/khaki-tigers-agree.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#added #nops Add Zircuit Configs diff --git a/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml b/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml new file mode 100644 index 0000000000..ec336d3efe --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml @@ -0,0 +1,36 @@ +ChainID = '48900' +ChainType = 'optimismBedrock' +FinalityTagEnabled = true +FinalityDepth = 1000 +LinkContractAddress = '0x5D6d033B4FbD2190D99D930719fAbAcB64d2439a' +LogPollInterval = "2s" +NoNewHeadsThreshold = "40s" +NoNewFinalizedHeadsThreshold = "15m" + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 24 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 2000 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 + +[Transactions.AutoPurge] +Enabled = true +Threshold = 90 # 90 blocks at 2s block time ~3 minutes +MinAttempts = 3 \ No newline at end of file diff --git a/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml b/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml new file mode 100644 index 0000000000..d6934d533d --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml @@ -0,0 +1,36 @@ +ChainID = '48899' +ChainType = 'optimismBedrock' +FinalityTagEnabled = true +FinalityDepth = 1000 +LinkContractAddress = '0xDEE94506570cA186BC1e3516fCf4fd719C312cCD' +LogPollInterval = "2s" +NoNewHeadsThreshold = "40s" +NoNewFinalizedHeadsThreshold = "15m" + +[GasEstimator] +EIP1559DynamicFees = true +PriceMin = '1 wei' +BumpMin = '100 wei' + +[GasEstimator.BlockHistory] +BlockHistorySize = 60 + +[Transactions] +ResendAfterThreshold = '30s' + +[HeadTracker] +HistoryDepth = 2000 + +[NodePool] +SyncThreshold = 10 + +[OCR] +ContractConfirmations = 1 + +[OCR2.Automation] +GasLimit = 6500000 + +[Transactions.AutoPurge] +Enabled = true +Threshold = 90 # 90 blocks at 2s block time ~3 minutes +MinAttempts = 3 \ No newline at end of file diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 028b152868..e854bcde44 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -6557,6 +6557,218 @@ GasLimit = 5400000

+
Zircuit Sepolia (48899)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'optimismBedrock' +FinalityDepth = 1000 +FinalityTagEnabled = true +LinkContractAddress = '0xDEE94506570cA186BC1e3516fCf4fd719C312cCD' +LogBackfillBatchSize = 1000 +LogPollInterval = '2s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 0 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '15m0s' + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '30s' + +[Transactions.AutoPurge] +Enabled = true +Threshold = 90 +MinAttempts = 3 + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '20 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 wei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '100 wei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = true +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 60 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + +[HeadTracker] +HistoryDepth = 2000 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = false +DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 1 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 +``` + +

+ +
Zircuit Mainnet (48900)

+ +```toml +AutoCreateKey = true +BlockBackfillDepth = 10 +BlockBackfillSkip = false +ChainType = 'optimismBedrock' +FinalityDepth = 1000 +FinalityTagEnabled = true +LinkContractAddress = '0x5D6d033B4FbD2190D99D930719fAbAcB64d2439a' +LogBackfillBatchSize = 1000 +LogPollInterval = '2s' +LogKeepBlocksDepth = 100000 +LogPrunePageSize = 0 +BackupLogPollerBlockDelay = 100 +MinIncomingConfirmations = 3 +MinContractPayment = '0.00001 link' +NonceAutoSync = true +NoNewHeadsThreshold = '40s' +LogBroadcasterEnabled = true +RPCDefaultBatchSize = 250 +RPCBlockQueryDelay = 1 +FinalizedBlockOffset = 0 +NoNewFinalizedHeadsThreshold = '15m0s' + +[Transactions] +ForwardersEnabled = false +MaxInFlight = 16 +MaxQueued = 250 +ReaperInterval = '1h0m0s' +ReaperThreshold = '168h0m0s' +ResendAfterThreshold = '30s' + +[Transactions.AutoPurge] +Enabled = true +Threshold = 90 +MinAttempts = 3 + +[BalanceMonitor] +Enabled = true + +[GasEstimator] +Mode = 'BlockHistory' +PriceDefault = '20 gwei' +PriceMax = '115792089237316195423570985008687907853269984665.640564039457584007913129639935 tether' +PriceMin = '1 wei' +LimitDefault = 500000 +LimitMax = 500000 +LimitMultiplier = '1' +LimitTransfer = 21000 +EstimateLimit = false +BumpMin = '100 wei' +BumpPercent = 20 +BumpThreshold = 3 +EIP1559DynamicFees = true +FeeCapDefault = '100 gwei' +TipCapDefault = '1 wei' +TipCapMin = '1 wei' + +[GasEstimator.BlockHistory] +BatchSize = 25 +BlockHistorySize = 24 +CheckInclusionBlocks = 12 +CheckInclusionPercentile = 90 +TransactionPercentile = 60 + +[GasEstimator.FeeHistory] +CacheTimeout = '10s' + +[HeadTracker] +HistoryDepth = 2000 +MaxBufferSize = 3 +SamplingInterval = '1s' +MaxAllowedFinalityDepth = 10000 +FinalityTagBypass = true + +[NodePool] +PollFailureThreshold = 5 +PollInterval = '10s' +SelectionMode = 'HighestHead' +SyncThreshold = 10 +LeaseDuration = '0s' +NodeIsSyncingEnabled = false +FinalizedBlockPollInterval = '5s' +EnforceRepeatableRead = false +DeathDeclarationDelay = '10s' +NewHeadsPollInterval = '0s' + +[OCR] +ContractConfirmations = 1 +ContractTransmitterTransmitTimeout = '10s' +DatabaseTimeout = '10s' +DeltaCOverride = '168h0m0s' +DeltaCJitterOverride = '1h0m0s' +ObservationGracePeriod = '1s' + +[OCR2] +[OCR2.Automation] +GasLimit = 6500000 + +[Workflow] +GasLimitDefault = 400000 +``` + +

+
Linea Goerli (59140)

```toml From 2f7705c99b130838e2bb238986857418d1af4208 Mon Sep 17 00:00:00 2001 From: Jaden Foldesi Date: Wed, 25 Sep 2024 22:39:51 +0900 Subject: [PATCH 15/31] Use Astar Chain Type (#1465) ## Motivation Set ChainType='astar' to use custom finality on mainnet ## Solution --------- Co-authored-by: simsonraj --- core/chains/evm/config/toml/defaults/Astar_Mainnet.toml | 4 ++-- docs/CONFIG.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/chains/evm/config/toml/defaults/Astar_Mainnet.toml b/core/chains/evm/config/toml/defaults/Astar_Mainnet.toml index 2d356d9564..d1697d137a 100644 --- a/core/chains/evm/config/toml/defaults/Astar_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Astar_Mainnet.toml @@ -1,6 +1,6 @@ ChainID = '592' -# FinalityTag will be enabled once the custom tag is implemented -# FinalityTagEnabled = true +ChainType = 'astar' +FinalityTagEnabled = true FinalityDepth = 100 LogPollInterval = '6s' diff --git a/docs/CONFIG.md b/docs/CONFIG.md index e854bcde44..52ff148c6c 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -4225,6 +4225,7 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false +ChainType = 'astar' FinalityDepth = 100 FinalityTagEnabled = false LogBackfillBatchSize = 1000 From e8e20f1bbe04b08a45571bdeb87e8c0b9e4f04ed Mon Sep 17 00:00:00 2001 From: Zubin Date: Thu, 12 Sep 2024 18:40:16 +0900 Subject: [PATCH 16/31] Zp/update mockrouter (#1427) Specifically, allow for empty message with zero gas. ## Motivation EVM2EVMOffRamp checks the message for content and gaslimit and skips calling ccip receive if they're not present. Consequently the mock errors (ReceiverError) when sending only tokens to a smart contract. This update will help chainlink-local correctly process fork-based tests too. ## Solution The MockRouter implementation of _routeMessage was last updated in https://github.com/smartcontractkit/ccip/pull/669 and is out of sync with the logic in EVM2EVMOffRamp. --- contracts/gas-snapshots/ccip.gas-snapshot | 72 +++++++++---------- .../src/v0.8/ccip/test/mocks/MockRouter.sol | 15 +++- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index 7e79a922d3..8c84c95305 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -103,7 +103,7 @@ CommitStore_report:test_OnlyPriceUpdateStaleReport_Revert() (gas: 59049) CommitStore_report:test_OnlyTokenPriceUpdates_Success() (gas: 53251) CommitStore_report:test_Paused_Revert() (gas: 21259) CommitStore_report:test_ReportAndPriceUpdate_Success() (gas: 84242) -CommitStore_report:test_ReportOnlyRootSuccess_gas() (gas: 56313) +CommitStore_report:test_ReportOnlyRootSuccess_gas() (gas: 56249) CommitStore_report:test_RootAlreadyCommitted_Revert() (gas: 63969) CommitStore_report:test_StaleReportWithRoot_Success() (gas: 119420) CommitStore_report:test_Unhealthy_Revert() (gas: 44751) @@ -123,13 +123,13 @@ CommitStore_verify:test_Paused_Revert() (gas: 18496) CommitStore_verify:test_TooManyLeaves_Revert() (gas: 36785) DefensiveExampleTest:test_HappyPath_Success() (gas: 200018) DefensiveExampleTest:test_Recovery() (gas: 424256) -E2E:test_E2E_3MessagesSuccess_gas() (gas: 1104303) +E2E:test_E2E_3MessagesSuccess_gas() (gas: 1133785) EVM2EVMOffRamp__releaseOrMintToken:test__releaseOrMintToken_NotACompatiblePool_Revert() (gas: 37797) EVM2EVMOffRamp__releaseOrMintToken:test__releaseOrMintToken_Success() (gas: 103733) -EVM2EVMOffRamp__releaseOrMintToken:test__releaseOrMintToken_TokenHandlingError_transfer_Revert() (gas: 85258) +EVM2EVMOffRamp__releaseOrMintToken:test__releaseOrMintToken_TokenHandlingError_transfer_Revert() (gas: 82758) EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_InvalidDataLength_Revert() (gas: 36786) EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() (gas: 94302) -EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 39768) +EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 37268) EVM2EVMOffRamp__releaseOrMintToken:test_releaseOrMintToken_skip_ReleaseOrMintBalanceMismatch_if_pool_Revert() (gas: 86559) EVM2EVMOffRamp__releaseOrMintTokens:test_OverValueWithARLOff_Success() (gas: 385308) EVM2EVMOffRamp__releaseOrMintTokens:test_PriceNotFoundForToken_Reverts() (gas: 141902) @@ -162,13 +162,13 @@ EVM2EVMOffRamp_execute:test_RootNotCommitted_Revert() (gas: 42539) EVM2EVMOffRamp_execute:test_SingleMessageNoTokensUnordered_Success() (gas: 157347) EVM2EVMOffRamp_execute:test_SingleMessageNoTokens_Success() (gas: 172692) EVM2EVMOffRamp_execute:test_SingleMessageToNonCCIPReceiver_Success() (gas: 247069) -EVM2EVMOffRamp_execute:test_SingleMessagesNoTokensSuccess_gas() (gas: 114153) +EVM2EVMOffRamp_execute:test_SingleMessagesNoTokensSuccess_gas() (gas: 113842) EVM2EVMOffRamp_execute:test_SkippedIncorrectNonceStillExecutes_Success() (gas: 407507) EVM2EVMOffRamp_execute:test_SkippedIncorrectNonce_Success() (gas: 54096) EVM2EVMOffRamp_execute:test_StrictUntouchedToSuccess_Success() (gas: 131047) EVM2EVMOffRamp_execute:test_TokenDataMismatch_Revert() (gas: 52090) EVM2EVMOffRamp_execute:test_TwoMessagesWithTokensAndGE_Success() (gas: 563497) -EVM2EVMOffRamp_execute:test_TwoMessagesWithTokensSuccess_gas() (gas: 495620) +EVM2EVMOffRamp_execute:test_TwoMessagesWithTokensSuccess_gas() (gas: 494294) EVM2EVMOffRamp_execute:test_UnexpectedTokenData_Revert() (gas: 35383) EVM2EVMOffRamp_execute:test_Unhealthy_Revert() (gas: 544753) EVM2EVMOffRamp_execute:test_UnsupportedNumberOfTokens_Revert() (gas: 64326) @@ -178,7 +178,7 @@ EVM2EVMOffRamp_execute:test_execute_RouterYULCall_Success() (gas: 427337) EVM2EVMOffRamp_executeSingleMessage:test_MessageSender_Revert() (gas: 18502) EVM2EVMOffRamp_executeSingleMessage:test_NonContractWithTokens_Success() (gas: 278153) EVM2EVMOffRamp_executeSingleMessage:test_NonContract_Success() (gas: 18659) -EVM2EVMOffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 221449) +EVM2EVMOffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 223949) EVM2EVMOffRamp_executeSingleMessage:test_ZeroGasDONExecution_Revert() (gas: 47881) EVM2EVMOffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens_Success() (gas: 47352) EVM2EVMOffRamp_executeSingleMessage:test_executeSingleMessage_WithTokens_Success() (gas: 313973) @@ -230,21 +230,21 @@ EVM2EVMOnRamp_forwardFromRouter:test_MaxFeeBalanceReached_Revert() (gas: 36457) EVM2EVMOnRamp_forwardFromRouter:test_MessageGasLimitTooHigh_Revert() (gas: 29015) EVM2EVMOnRamp_forwardFromRouter:test_MessageTooLarge_Revert() (gas: 107571) EVM2EVMOnRamp_forwardFromRouter:test_OriginalSender_Revert() (gas: 22679) -EVM2EVMOnRamp_forwardFromRouter:test_OverValueWithARLOff_Success() (gas: 224625) +EVM2EVMOnRamp_forwardFromRouter:test_OverValueWithARLOff_Success() (gas: 227125) EVM2EVMOnRamp_forwardFromRouter:test_Paused_Revert() (gas: 53072) EVM2EVMOnRamp_forwardFromRouter:test_Permissions_Revert() (gas: 25481) EVM2EVMOnRamp_forwardFromRouter:test_PriceNotFoundForToken_Revert() (gas: 59347) EVM2EVMOnRamp_forwardFromRouter:test_ShouldIncrementNonceOnlyOnOrdered_Success() (gas: 179148) EVM2EVMOnRamp_forwardFromRouter:test_ShouldIncrementSeqNumAndNonce_Success() (gas: 177430) EVM2EVMOnRamp_forwardFromRouter:test_ShouldStoreNonLinkFees() (gas: 137322) -EVM2EVMOnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3822827) +EVM2EVMOnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3825327) EVM2EVMOnRamp_forwardFromRouter:test_TooManyTokens_Revert() (gas: 30187) EVM2EVMOnRamp_forwardFromRouter:test_Unhealthy_Revert() (gas: 43300) EVM2EVMOnRamp_forwardFromRouter:test_UnsupportedToken_Revert() (gas: 109283) EVM2EVMOnRamp_forwardFromRouter:test_ZeroAddressReceiver_Revert() (gas: 312579) EVM2EVMOnRamp_forwardFromRouter:test_forwardFromRouter_ShouldStoreLinkFees_Success() (gas: 112322) EVM2EVMOnRamp_forwardFromRouter:test_forwardFromRouter_UnsupportedToken_Revert() (gas: 72206) -EVM2EVMOnRamp_forwardFromRouter:test_forwardFromRouter_correctSourceTokenData_Success() (gas: 710531) +EVM2EVMOnRamp_forwardFromRouter:test_forwardFromRouter_correctSourceTokenData_Success() (gas: 713031) EVM2EVMOnRamp_forwardFromRouter_upgrade:test_V2NonceNewSenderStartsAtZero_Success() (gas: 147664) EVM2EVMOnRamp_forwardFromRouter_upgrade:test_V2NonceStartsAtV1Nonce_Success() (gas: 190529) EVM2EVMOnRamp_forwardFromRouter_upgrade:test_V2SenderNoncesReadsPreviousRamp_Success() (gas: 121320) @@ -275,7 +275,7 @@ EVM2EVMOnRamp_getTokenTransferCost:test__getTokenTransferCost_selfServeUsesDefau EVM2EVMOnRamp_linkAvailableForPayment:test_InsufficientLinkBalance_Success() (gas: 32615) EVM2EVMOnRamp_linkAvailableForPayment:test_LinkAvailableForPayment_Success() (gas: 134833) EVM2EVMOnRamp_payNops:test_AdminPayNops_Success() (gas: 143159) -EVM2EVMOnRamp_payNops:test_InsufficientBalance_Revert() (gas: 26543) +EVM2EVMOnRamp_payNops:test_InsufficientBalance_Revert() (gas: 29043) EVM2EVMOnRamp_payNops:test_NoFeesToPay_Revert() (gas: 127367) EVM2EVMOnRamp_payNops:test_NoNopsToPay_Revert() (gas: 133251) EVM2EVMOnRamp_payNops:test_NopPayNops_Success() (gas: 146446) @@ -307,7 +307,7 @@ EVM2EVMOnRamp_withdrawNonLinkFees:test_SettlingBalance_Success() (gas: 272035) EVM2EVMOnRamp_withdrawNonLinkFees:test_WithdrawNonLinkFees_Success() (gas: 53446) EVM2EVMOnRamp_withdrawNonLinkFees:test_WithdrawToZeroAddress_Revert() (gas: 12830) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_fallbackToWethTransfer() (gas: 96729) -EtherSenderReceiverTest_ccipReceive:test_ccipReceive_happyPath() (gas: 47688) +EtherSenderReceiverTest_ccipReceive:test_ccipReceive_happyPath() (gas: 49688) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_wrongToken() (gas: 17384) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_wrongTokenAmount() (gas: 15677) EtherSenderReceiverTest_ccipSend:test_ccipSend_reverts_insufficientFee_feeToken() (gas: 99741) @@ -360,9 +360,9 @@ MerkleMultiProofTest:test_MerkleRootSingleLeaf_Success() (gas: 3649) MerkleMultiProofTest:test_SpecSync_gas() (gas: 34123) MockRouterTest:test_ccipSendWithInsufficientNativeTokens_Revert() (gas: 33965) MockRouterTest:test_ccipSendWithInvalidMsgValue_Revert() (gas: 60758) -MockRouterTest:test_ccipSendWithLinkFeeTokenAndValidMsgValue_Success() (gas: 126294) +MockRouterTest:test_ccipSendWithLinkFeeTokenAndValidMsgValue_Success() (gas: 126354) MockRouterTest:test_ccipSendWithLinkFeeTokenbutInsufficientAllowance_Revert() (gas: 63302) -MockRouterTest:test_ccipSendWithSufficientNativeFeeTokens_Success() (gas: 43853) +MockRouterTest:test_ccipSendWithSufficientNativeFeeTokens_Success() (gas: 43913) MultiAggregateRateLimiter_applyRateLimiterConfigUpdates:test_MultipleConfigsBothLanes_Success() (gas: 132031) MultiAggregateRateLimiter_applyRateLimiterConfigUpdates:test_MultipleConfigs_Success() (gas: 312057) MultiAggregateRateLimiter_applyRateLimiterConfigUpdates:test_OnlyCallableByOwner_Revert() (gas: 17717) @@ -388,7 +388,7 @@ MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitExce MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitReset_Success() (gas: 78780) MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithTokensOnDifferentChains_Success() (gas: 312061) MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithTokens_Success() (gas: 54784) -MultiAggregateRateLimiter_onOutboundMessage:test_RateLimitValueDifferentLanes_Success() (gas: 9223372036854754743) +MultiAggregateRateLimiter_onOutboundMessage:test_RateLimitValueDifferentLanes_Success() (gas: 53241) MultiAggregateRateLimiter_onOutboundMessage:test_ValidateMessageWithNoTokens_Success() (gas: 19104) MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageFromUnauthorizedCaller_Revert() (gas: 15778) MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithDifferentTokensOnDifferentChains_Success() (gas: 213732) @@ -430,16 +430,16 @@ MultiOCR3Base_transmit:test_InsufficientSignatures_Revert() (gas: 76930) MultiOCR3Base_transmit:test_NonUniqueSignature_Revert() (gas: 66127) MultiOCR3Base_transmit:test_SignatureOutOfRegistration_Revert() (gas: 33419) MultiOCR3Base_transmit:test_TooManySignatures_Revert() (gas: 79521) -MultiOCR3Base_transmit:test_TransmitSigners_gas_Success() (gas: 34131) +MultiOCR3Base_transmit:test_TransmitSigners_gas_Success() (gas: 34020) MultiOCR3Base_transmit:test_TransmitWithExtraCalldataArgs_Revert() (gas: 47114) MultiOCR3Base_transmit:test_TransmitWithLessCalldataArgs_Revert() (gas: 25682) -MultiOCR3Base_transmit:test_TransmitWithoutSignatureVerification_gas_Success() (gas: 18726) +MultiOCR3Base_transmit:test_TransmitWithoutSignatureVerification_gas_Success() (gas: 18714) MultiOCR3Base_transmit:test_UnAuthorizedTransmitter_Revert() (gas: 24191) MultiOCR3Base_transmit:test_UnauthorizedSigner_Revert() (gas: 61409) MultiOCR3Base_transmit:test_UnconfiguredPlugin_Revert() (gas: 39890) MultiOCR3Base_transmit:test_ZeroSignatures_Revert() (gas: 32973) MultiOnRampTokenPoolReentrancy:test_OnRampTokenPoolReentrancy_Success() (gas: 393335) -MultiRampsE2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1448084) +MultiRampsE2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1461081) NonceManager_NonceIncrementation:test_getIncrementedOutboundNonce_Success() (gas: 37907) NonceManager_NonceIncrementation:test_incrementInboundNonce_Skip() (gas: 23694) NonceManager_NonceIncrementation:test_incrementInboundNonce_Success() (gas: 38763) @@ -469,7 +469,7 @@ OCR2BaseNoChecks_setOCR2Config:test_TooManyTransmitter_Revert() (gas: 36938) OCR2BaseNoChecks_setOCR2Config:test_TransmitterCannotBeZeroAddress_Revert() (gas: 24158) OCR2BaseNoChecks_transmit:test_ConfigDigestMismatch_Revert() (gas: 17448) OCR2BaseNoChecks_transmit:test_ForkedChain_Revert() (gas: 26726) -OCR2BaseNoChecks_transmit:test_TransmitSuccess_gas() (gas: 27478) +OCR2BaseNoChecks_transmit:test_TransmitSuccess_gas() (gas: 27466) OCR2BaseNoChecks_transmit:test_UnAuthorizedTransmitter_Revert() (gas: 21296) OCR2Base_setOCR2Config:test_FMustBePositive_Revert() (gas: 12189) OCR2Base_setOCR2Config:test_FTooHigh_Revert() (gas: 12345) @@ -483,16 +483,16 @@ OCR2Base_transmit:test_ConfigDigestMismatch_Revert() (gas: 19623) OCR2Base_transmit:test_ForkedChain_Revert() (gas: 37683) OCR2Base_transmit:test_NonUniqueSignature_Revert() (gas: 55309) OCR2Base_transmit:test_SignatureOutOfRegistration_Revert() (gas: 20962) -OCR2Base_transmit:test_Transmit2SignersSuccess_gas() (gas: 51686) +OCR2Base_transmit:test_Transmit2SignersSuccess_gas() (gas: 51674) OCR2Base_transmit:test_UnAuthorizedTransmitter_Revert() (gas: 23484) OCR2Base_transmit:test_UnauthorizedSigner_Revert() (gas: 39665) OCR2Base_transmit:test_WrongNumberOfSignatures_Revert() (gas: 20557) OffRamp__releaseOrMintSingleToken:test__releaseOrMintSingleToken_NotACompatiblePool_Revert() (gas: 38408) OffRamp__releaseOrMintSingleToken:test__releaseOrMintSingleToken_Success() (gas: 106250) -OffRamp__releaseOrMintSingleToken:test__releaseOrMintSingleToken_TokenHandlingError_transfer_Revert() (gas: 87409) +OffRamp__releaseOrMintSingleToken:test__releaseOrMintSingleToken_TokenHandlingError_transfer_Revert() (gas: 84909) OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_InvalidDataLength_Revert() (gas: 38954) OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() (gas: 96511) -OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 41956) +OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() (gas: 39456) OffRamp__releaseOrMintSingleToken:test_releaseOrMintToken_skip_ReleaseOrMintBalanceMismatch_if_pool_Revert() (gas: 88684) OffRamp_applySourceChainConfigUpdates:test_AddMultipleChains_Success() (gas: 468115) OffRamp_applySourceChainConfigUpdates:test_AddNewChain_Success() (gas: 99227) @@ -550,7 +550,7 @@ OffRamp_execute:test_ZeroReports_Revert() (gas: 17159) OffRamp_executeSingleMessage:test_MessageSender_Revert() (gas: 18190) OffRamp_executeSingleMessage:test_NonContractWithTokens_Success() (gas: 246556) OffRamp_executeSingleMessage:test_NonContract_Success() (gas: 20472) -OffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 205195) +OffRamp_executeSingleMessage:test_TokenHandlingError_Revert() (gas: 207695) OffRamp_executeSingleMessage:test_ZeroGasDONExecution_Revert() (gas: 48734) OffRamp_executeSingleMessage:test_executeSingleMessage_NoTokens_Success() (gas: 48257) OffRamp_executeSingleMessage:test_executeSingleMessage_WithFailingValidationNoRouterCall_Revert() (gas: 229631) @@ -572,12 +572,12 @@ OffRamp_executeSingleReport:test_SingleMessageNoTokensOtherChain_Success() (gas: OffRamp_executeSingleReport:test_SingleMessageNoTokensUnordered_Success() (gas: 187191) OffRamp_executeSingleReport:test_SingleMessageNoTokens_Success() (gas: 206771) OffRamp_executeSingleReport:test_SingleMessageToNonCCIPReceiver_Success() (gas: 263519) -OffRamp_executeSingleReport:test_SingleMessagesNoTokensSuccess_gas() (gas: 138408) +OffRamp_executeSingleReport:test_SingleMessagesNoTokensSuccess_gas() (gas: 138111) OffRamp_executeSingleReport:test_SkippedIncorrectNonceStillExecutes_Success() (gas: 409328) OffRamp_executeSingleReport:test_SkippedIncorrectNonce_Success() (gas: 65876) OffRamp_executeSingleReport:test_TokenDataMismatch_Revert() (gas: 80909) OffRamp_executeSingleReport:test_TwoMessagesWithTokensAndGE_Success() (gas: 566299) -OffRamp_executeSingleReport:test_TwoMessagesWithTokensSuccess_gas() (gas: 517689) +OffRamp_executeSingleReport:test_TwoMessagesWithTokensSuccess_gas() (gas: 516604) OffRamp_executeSingleReport:test_UnexpectedTokenData_Revert() (gas: 35742) OffRamp_executeSingleReport:test_UnhealthySingleChainCurse_Revert() (gas: 517721) OffRamp_executeSingleReport:test_Unhealthy_Revert() (gas: 515089) @@ -646,10 +646,10 @@ OnRamp_forwardFromRouter:test_ShouldIncrementNonceOnlyOnOrdered_Success() (gas: OnRamp_forwardFromRouter:test_ShouldIncrementSeqNumAndNonce_Success() (gas: 205670) OnRamp_forwardFromRouter:test_ShouldStoreLinkFees() (gas: 121815) OnRamp_forwardFromRouter:test_ShouldStoreNonLinkFees() (gas: 143193) -OnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3872608) +OnRamp_forwardFromRouter:test_SourceTokenDataTooLarge_Revert() (gas: 3875108) OnRamp_forwardFromRouter:test_UnsupportedToken_Revert() (gas: 108546) OnRamp_forwardFromRouter:test_forwardFromRouter_UnsupportedToken_Revert() (gas: 73975) -OnRamp_forwardFromRouter:test_forwardFromRouter_WithValidation_Success() (gas: 262685) +OnRamp_forwardFromRouter:test_forwardFromRouter_WithValidation_Success() (gas: 265185) OnRamp_getFee:test_EmptyMessage_Success() (gas: 104467) OnRamp_getFee:test_EnforceOutOfOrder_Revert() (gas: 74075) OnRamp_getFee:test_SingleTokenMessage_Success() (gas: 119799) @@ -662,7 +662,7 @@ OnRamp_setDynamicConfig:test_SetConfigInvalidConfig_Revert() (gas: 11112) OnRamp_setDynamicConfig:test_SetConfigOnlyOwner_Revert() (gas: 15939) OnRamp_setDynamicConfig:test_SetDynamicConfig_Success() (gas: 51996) OnRamp_withdrawFeeTokens:test_WithdrawFeeTokens_Success() (gas: 97214) -PingPong_ccipReceive:test_CcipReceive_Success() (gas: 150175) +PingPong_ccipReceive:test_CcipReceive_Success() (gas: 152675) PingPong_plumbing:test_OutOfOrderExecution_Success() (gas: 20277) PingPong_plumbing:test_Pausing_Success() (gas: 17777) PingPong_startPingPong:test_StartPingPong_With_OOO_Success() (gas: 163199) @@ -775,7 +775,7 @@ RMN_ownerUnbless:test_Unbless_Success() (gas: 74699) RMN_ownerUnvoteToCurse:test_CanBlessAndCurseAfterGlobalCurseIsLifted() (gas: 470965) RMN_ownerUnvoteToCurse:test_IsIdempotent() (gas: 397532) RMN_ownerUnvoteToCurse:test_NonOwner_Revert() (gas: 18591) -RMN_ownerUnvoteToCurse:test_OwnerUnvoteToCurseSuccess_gas() (gas: 357403) +RMN_ownerUnvoteToCurse:test_OwnerUnvoteToCurseSuccess_gas() (gas: 357400) RMN_ownerUnvoteToCurse:test_UnknownVoter_Revert() (gas: 32980) RMN_ownerUnvoteToCurse_Benchmark:test_OwnerUnvoteToCurse_1Voter_LiftsCurse_gas() (gas: 261985) RMN_permaBlessing:test_PermaBlessing() (gas: 202686) @@ -783,7 +783,7 @@ RMN_setConfig:test_BlessVoterIsZeroAddress_Revert() (gas: 15494) RMN_setConfig:test_EitherThresholdIsZero_Revert() (gas: 21095) RMN_setConfig:test_NonOwner_Revert() (gas: 14713) RMN_setConfig:test_RepeatedAddress_Revert() (gas: 18213) -RMN_setConfig:test_SetConfigSuccess_gas() (gas: 104204) +RMN_setConfig:test_SetConfigSuccess_gas() (gas: 104022) RMN_setConfig:test_TotalWeightsSmallerThanEachThreshold_Revert() (gas: 30173) RMN_setConfig:test_VoteToBlessByEjectedVoter_Revert() (gas: 130303) RMN_setConfig:test_VotersLengthIsZero_Revert() (gas: 12128) @@ -868,18 +868,18 @@ Router_getFee:test_GetFeeSupportedChain_Success() (gas: 46464) Router_getFee:test_UnsupportedDestinationChain_Revert() (gas: 17138) Router_getSupportedTokens:test_GetSupportedTokens_Revert() (gas: 10460) Router_recoverTokens:test_RecoverTokensInvalidRecipient_Revert() (gas: 11316) -Router_recoverTokens:test_RecoverTokensNoFunds_Revert() (gas: 17761) +Router_recoverTokens:test_RecoverTokensNoFunds_Revert() (gas: 20261) Router_recoverTokens:test_RecoverTokensNonOwner_Revert() (gas: 11159) Router_recoverTokens:test_RecoverTokensValueReceiver_Revert() (gas: 422138) -Router_recoverTokens:test_RecoverTokens_Success() (gas: 50437) +Router_recoverTokens:test_RecoverTokens_Success() (gas: 52437) Router_routeMessage:test_AutoExec_Success() (gas: 42684) Router_routeMessage:test_ExecutionEvent_Success() (gas: 157980) Router_routeMessage:test_ManualExec_Success() (gas: 35381) Router_routeMessage:test_OnlyOffRamp_Revert() (gas: 25116) Router_routeMessage:test_WhenNotHealthy_Revert() (gas: 44724) Router_setWrappedNative:test_OnlyOwner_Revert() (gas: 10985) -SelfFundedPingPong_ccipReceive:test_FundingIfNotANop_Revert() (gas: 53531) -SelfFundedPingPong_ccipReceive:test_Funding_Success() (gas: 417002) +SelfFundedPingPong_ccipReceive:test_FundingIfNotANop_Revert() (gas: 55531) +SelfFundedPingPong_ccipReceive:test_Funding_Success() (gas: 419502) SelfFundedPingPong_setCountIncrBeforeFunding:test_setCountIncrBeforeFunding() (gas: 20151) TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_OnlyPendingAdministrator_Revert() (gas: 51085) TokenAdminRegistry_acceptAdminRole:test_acceptAdminRole_Success() (gas: 43947) @@ -907,8 +907,8 @@ TokenPoolAndProxy:test_lockOrBurn_burnMint_Success() (gas: 6070353) TokenPoolAndProxy:test_lockOrBurn_burnWithFromMint_Success() (gas: 6101826) TokenPoolAndProxy:test_lockOrBurn_lockRelease_Success() (gas: 6319594) TokenPoolAndProxy:test_setPreviousPool_Success() (gas: 3387124) -TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_2() (gas: 6913796) -TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_4() (gas: 7097821) +TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_2() (gas: 6916296) +TokenPoolAndProxyMigration:test_tokenPoolMigration_Success_1_4() (gas: 7100321) TokenPoolWithAllowList_applyAllowListUpdates:test_AllowListNotEnabled_Revert() (gas: 2209837) TokenPoolWithAllowList_applyAllowListUpdates:test_OnlyOwner_Revert() (gas: 12089) TokenPoolWithAllowList_applyAllowListUpdates:test_SetAllowListSkipsZero_Success() (gas: 23324) diff --git a/contracts/src/v0.8/ccip/test/mocks/MockRouter.sol b/contracts/src/v0.8/ccip/test/mocks/MockRouter.sol index 050cd6d457..9181fb37c2 100644 --- a/contracts/src/v0.8/ccip/test/mocks/MockRouter.sol +++ b/contracts/src/v0.8/ccip/test/mocks/MockRouter.sol @@ -45,8 +45,19 @@ contract MockCCIPRouter is IRouter, IRouterClient { uint256 gasLimit, address receiver ) internal returns (bool success, bytes memory retData, uint256 gasUsed) { - // Only send through the router if the receiver is a contract and implements the IAny2EVMMessageReceiver interface. - if (receiver.code.length == 0 || !receiver.supportsInterface(type(IAny2EVMMessageReceiver).interfaceId)) { + // There are three cases in which we skip calling the receiver: + // 1. If the message data is empty AND the gas limit is 0. + // This indicates a message that only transfers tokens. It is valid to only send tokens to a contract + // that supports the IAny2EVMMessageReceiver interface, but without this first check we would call the + // receiver without any gas, which would revert the transaction. + // 2. If the receiver is not a contract. + // 3. If the receiver is a contract but it does not support the IAny2EVMMessageReceiver interface. + // + // The ordering of these checks is important, as the first check is the cheapest to execute. + if ( + (message.data.length == 0 && gasLimit == 0) || receiver.code.length == 0 + || !receiver.supportsInterface(type(IAny2EVMMessageReceiver).interfaceId) + ) { return (true, "", 0); } From da80ec5525f47e316e22614d320884f88e1f61e0 Mon Sep 17 00:00:00 2001 From: "valerii.kabisov" Date: Thu, 3 Oct 2024 22:01:41 +0900 Subject: [PATCH 17/31] Bump version for CCIP 2.14.0-ccip1.5.3 --- contracts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/package.json b/contracts/package.json index 334b75701c..efbf79e8ef 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@chainlink/contracts-ccip", - "version": "1.5.0", + "version": "2.14.0-ccip1.5.3", "description": "Chainlink CCIP smart contracts", "author": "Chainlink devs", "license": "BUSL-1.1", From ad546bdedcce29e073dd4d7472c1b71bb8f972ac Mon Sep 17 00:00:00 2001 From: Valerii Kabisov <172247313+valerii-kabisov-cll@users.noreply.github.com> Date: Thu, 29 Aug 2024 23:12:58 +0900 Subject: [PATCH 18/31] [CCIP-2590] Transfer Data Availability costs to the DAGasEstimator (#1161) ## Motivation We use DAGasPriceEstimator to roughly estimate execution cost of a message in the Exec plugin. During this execution cost estimation, we need to account for the data availability component: ``` type DAGasPriceEstimator struct { daOverheadGas int64 gasPerDAByte int64 daMultiplier int64 } ``` however, these fields are not used atm, they remain 0 during cost estimation. The challenge is they live in OnRamp.sol DynamicConfig, ``` struct DynamicConfig { uint32 destDataAvailabilityOverheadGas; // Extra data availability gas charged on top of the message, e.g. for OCR uint16 destGasPerDataAvailabilityByte; // Amount of gas to charge per byte of message data that needs availability uint16 destDataAvailabilityMultiplierBps; // Multiplier for data availability gas, multiples of bps, or 0.0001 } ``` We need to read them via the OnRamp reader and pass that into the DAGasPriceEstimator one way or another. ## Solution New service implemented to transfer onRamp config to offRamp/commit plugins. It provides weak dependency between plugins and provides actual data on demand. --- .mockery.yaml | 3 + .../ocr2/plugins/ccip/ccipcommit/ocr2_test.go | 12 +- .../plugins/ccip/ccipexec/initializers.go | 4 +- .../ocr2/plugins/ccip/ccipexec/ocr2_test.go | 19 ++-- .../plugins/ccip/estimatorconfig/config.go | 49 ++++++++ .../ccip/estimatorconfig/config_test.go | 45 ++++++++ .../ocr2/plugins/ccip/exportinternal.go | 16 +-- .../ccip/internal/cache/commit_roots_test.go | 14 ++- .../ccipdata/commit_store_reader_test.go | 13 ++- .../internal/ccipdata/factory/commit_store.go | 14 +-- .../ccipdata/factory/commit_store_test.go | 8 +- .../ccip/internal/ccipdata/factory/offramp.go | 16 +-- .../internal/ccipdata/factory/offramp_test.go | 7 +- .../internal/ccipdata/fee_estimator_config.go | 9 ++ .../mocks/fee_estimator_config_mock.go | 106 ++++++++++++++++++ .../internal/ccipdata/offramp_reader_test.go | 11 +- .../ccip/internal/ccipdata/v1_0_0/offramp.go | 10 +- .../ccipdata/v1_0_0/offramp_reader_test.go | 5 +- .../v1_0_0/offramp_reader_unit_test.go | 11 +- .../ccip/internal/ccipdata/v1_0_0/onramp.go | 80 ++++++------- .../internal/ccipdata/v1_2_0/commit_store.go | 15 ++- .../ccipdata/v1_2_0/commit_store_test.go | 6 +- .../ccip/internal/ccipdata/v1_2_0/offramp.go | 22 +++- .../ccipdata/v1_2_0/offramp_reader_test.go | 5 +- .../ccip/internal/ccipdata/v1_2_0/onramp.go | 77 ++++++------- .../internal/ccipdata/v1_5_0/commit_store.go | 12 +- .../ccip/internal/ccipdata/v1_5_0/offramp.go | 20 +++- .../ccip/internal/ccipdata/v1_5_0/onramp.go | 79 ++++++------- .../plugins/ccip/prices/da_price_estimator.go | 26 +++-- .../ccip/prices/da_price_estimator_test.go | 104 +++++++++++------ .../ccip/prices/gas_price_estimator.go | 5 +- core/services/relay/evm/ccip.go | 38 ++++--- core/services/relay/evm/commit_provider.go | 46 +++++--- core/services/relay/evm/evm.go | 9 ++ core/services/relay/evm/exec_provider.go | 22 +++- 35 files changed, 652 insertions(+), 286 deletions(-) create mode 100644 core/services/ocr2/plugins/ccip/estimatorconfig/config.go create mode 100644 core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go create mode 100644 core/services/ocr2/plugins/ccip/internal/ccipdata/fee_estimator_config.go create mode 100644 core/services/ocr2/plugins/ccip/internal/ccipdata/mocks/fee_estimator_config_mock.go diff --git a/.mockery.yaml b/.mockery.yaml index 39e7477a6d..3911f3b6f5 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -484,6 +484,9 @@ packages: PriceRegistryReader: config: filename: price_registry_reader_mock.go + FeeEstimatorConfigReader: + config: + filename: fee_estimator_config_mock.go TokenPoolReader: config: filename: token_pool_reader_mock.go diff --git a/core/services/ocr2/plugins/ccip/ccipcommit/ocr2_test.go b/core/services/ocr2/plugins/ccip/ccipcommit/ocr2_test.go index f1ca4d91cd..b02b7138a1 100644 --- a/core/services/ocr2/plugins/ccip/ccipcommit/ocr2_test.go +++ b/core/services/ocr2/plugins/ccip/ccipcommit/ocr2_test.go @@ -20,15 +20,13 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/hashutil" "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/mocks" mocks2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" @@ -45,7 +43,6 @@ import ( ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0" - ccipdbmocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdb/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices" ) @@ -410,7 +407,9 @@ func TestCommitReportingPlugin_Report(t *testing.T) { evmEstimator := mocks.NewEvmFeeEstimator(t) evmEstimator.On("L1Oracle").Return(nil) - gasPriceEstimator := prices.NewDAGasPriceEstimator(evmEstimator, nil, 2e9, 2e9) // 200% deviation + + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + gasPriceEstimator := prices.NewDAGasPriceEstimator(evmEstimator, nil, 2e9, 2e9, feeEstimatorConfig) // 200% deviation var destTokens []cciptypes.Address for tk := range tc.tokenDecimals { @@ -434,7 +433,7 @@ func TestCommitReportingPlugin_Report(t *testing.T) { })).Return(destDecimals, nil).Maybe() lp := mocks2.NewLogPoller(t) - commitStoreReader, err := v1_2_0.NewCommitStore(logger.TestLogger(t), utils.RandomAddress(), nil, lp) + commitStoreReader, err := v1_2_0.NewCommitStore(logger.TestLogger(t), utils.RandomAddress(), nil, lp, feeEstimatorConfig) assert.NoError(t, err) healthCheck := ccipcachemocks.NewChainHealthcheck(t) @@ -1532,6 +1531,7 @@ func TestCommitReportingPlugin_calculatePriceUpdates(t *testing.T) { nil, tc.daGasPriceDeviationPPB, tc.execGasPriceDeviationPPB, + ccipdatamocks.NewFeeEstimatorConfigReader(t), ) r := &CommitReportingPlugin{ diff --git a/core/services/ocr2/plugins/ccip/ccipexec/initializers.go b/core/services/ocr2/plugins/ccip/ccipexec/initializers.go index c8d079b263..aa42ff2828 100644 --- a/core/services/ocr2/plugins/ccip/ccipexec/initializers.go +++ b/core/services/ocr2/plugins/ccip/ccipexec/initializers.go @@ -18,8 +18,6 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/cache" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/statuschecker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" @@ -27,6 +25,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/cache" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/factory" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/observability" diff --git a/core/services/ocr2/plugins/ccip/ccipexec/ocr2_test.go b/core/services/ocr2/plugins/ccip/ccipexec/ocr2_test.go index 84cb73c664..627afda975 100644 --- a/core/services/ocr2/plugins/ccip/ccipexec/ocr2_test.go +++ b/core/services/ocr2/plugins/ccip/ccipexec/ocr2_test.go @@ -16,17 +16,18 @@ import ( mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/libocr/commontypes" - "github.com/smartcontractkit/libocr/offchainreporting2/types" - ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + "github.com/smartcontractkit/libocr/commontypes" + "github.com/smartcontractkit/libocr/offchainreporting2/types" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" lpMocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/cache" @@ -41,8 +42,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/testhelpers" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata" - - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" ) func TestExecutionReportingPlugin_Observation(t *testing.T) { @@ -428,8 +427,10 @@ func TestExecutionReportingPlugin_buildReport(t *testing.T) { p.metricsCollector = ccip.NoopMetricsCollector p.commitStoreReader = commitStore + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + lp := lpMocks.NewLogPoller(t) - offRampReader, err := v1_0_0.NewOffRamp(logger.TestLogger(t), utils.RandomAddress(), nil, lp, nil, nil) + offRampReader, err := v1_0_0.NewOffRamp(logger.TestLogger(t), utils.RandomAddress(), nil, lp, nil, nil, feeEstimatorConfig) assert.NoError(t, err) p.offRampReader = offRampReader @@ -1376,7 +1377,9 @@ func Test_prepareTokenExecData(t *testing.T) { } func encodeExecutionReport(t *testing.T, report cciptypes.ExecReport) []byte { - reader, err := v1_2_0.NewOffRamp(logger.TestLogger(t), utils.RandomAddress(), nil, nil, nil, nil) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + + reader, err := v1_2_0.NewOffRamp(logger.TestLogger(t), utils.RandomAddress(), nil, nil, nil, nil, feeEstimatorConfig) require.NoError(t, err) ctx := testutils.Context(t) encodedReport, err := reader.EncodeExecutionReport(ctx, report) diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/config.go b/core/services/ocr2/plugins/ccip/estimatorconfig/config.go new file mode 100644 index 0000000000..2eda88d441 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/config.go @@ -0,0 +1,49 @@ +package estimatorconfig + +import ( + "context" + "errors" + + "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" +) + +// FeeEstimatorConfigProvider implements abstract storage for the DataAvailability settings in onRamp dynamic Config. +// It's implemented to transfer DA config from different entities offRamp, onRamp, commitStore without injecting the +// strong dependency between modules. ConfigProvider fetch ccip.OnRampReader object reads and returns only relevant +// fields for the daGasEstimator from the encapsulated onRampReader. +type FeeEstimatorConfigProvider interface { + SetOnRampReader(reader ccip.OnRampReader) + GetDataAvailabilityConfig(ctx context.Context) (destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps int64, err error) +} + +type FeeEstimatorConfigService struct { + onRampReader ccip.OnRampReader +} + +func NewFeeEstimatorConfigService() *FeeEstimatorConfigService { + return &FeeEstimatorConfigService{} +} + +// SetOnRampReader Sets the onRamp reader instance. +// must be called once for each instance. +func (c *FeeEstimatorConfigService) SetOnRampReader(reader ccip.OnRampReader) { + c.onRampReader = reader +} + +// GetDataAvailabilityConfig Returns dynamic config data availability parameters. +// GetDynamicConfig should be cached in the onRamp reader to avoid unnecessary on-chain calls +func (c *FeeEstimatorConfigService) GetDataAvailabilityConfig(ctx context.Context) (destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps int64, err error) { + if c.onRampReader == nil { + return 0, 0, 0, errors.New("no OnRampReader has been configured") + } + + cfg, err := c.onRampReader.GetDynamicConfig(ctx) + if err != nil { + return 0, 0, 0, err + } + + return int64(cfg.DestDataAvailabilityOverheadGas), + int64(cfg.DestGasPerDataAvailabilityByte), + int64(cfg.DestDataAvailabilityMultiplierBps), + err +} diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go b/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go new file mode 100644 index 0000000000..05226f8b48 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go @@ -0,0 +1,45 @@ +package estimatorconfig_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" +) + +func TestFeeEstimatorConfigService(t *testing.T) { + svc := estimatorconfig.NewFeeEstimatorConfigService() + ctx := context.Background() + + var expectedDestDataAvailabilityOverheadGas int64 = 1 + var expectedDestGasPerDataAvailabilityByte int64 = 2 + var expectedDestDataAvailabilityMultiplierBps int64 = 3 + + onRampReader := mocks.NewOnRampReader(t) + _, _, _, err := svc.GetDataAvailabilityConfig(ctx) + require.Error(t, err) + svc.SetOnRampReader(onRampReader) + + onRampReader.On("GetDynamicConfig", ctx). + Return(ccip.OnRampDynamicConfig{ + DestDataAvailabilityOverheadGas: uint32(expectedDestDataAvailabilityOverheadGas), + DestGasPerDataAvailabilityByte: uint16(expectedDestGasPerDataAvailabilityByte), + DestDataAvailabilityMultiplierBps: uint16(expectedDestDataAvailabilityMultiplierBps), + }, nil).Once() + + destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps, err := svc.GetDataAvailabilityConfig(ctx) + require.NoError(t, err) + require.Equal(t, expectedDestDataAvailabilityOverheadGas, destDataAvailabilityOverheadGas) + require.Equal(t, expectedDestGasPerDataAvailabilityByte, destGasPerDataAvailabilityByte) + require.Equal(t, expectedDestDataAvailabilityMultiplierBps, destDataAvailabilityMultiplierBps) + + onRampReader.On("GetDynamicConfig", ctx). + Return(ccip.OnRampDynamicConfig{}, errors.New("test")).Once() + _, _, _, err = svc.GetDataAvailabilityConfig(ctx) + require.Error(t, err) +} diff --git a/core/services/ocr2/plugins/ccip/exportinternal.go b/core/services/ocr2/plugins/ccip/exportinternal.go index 2a5767ac85..2f924085fb 100644 --- a/core/services/ocr2/plugins/ccip/exportinternal.go +++ b/core/services/ocr2/plugins/ccip/exportinternal.go @@ -37,20 +37,20 @@ func NewEvmPriceRegistry(lp logpoller.LogPoller, ec client.Client, lggr logger.L type VersionFinder = factory.VersionFinder -func NewCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address ccip.Address, ec client.Client, lp logpoller.LogPoller) (ccipdata.CommitStoreReader, error) { - return factory.NewCommitStoreReader(lggr, versionFinder, address, ec, lp) +func NewCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address ccip.Address, ec client.Client, lp logpoller.LogPoller, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) (ccipdata.CommitStoreReader, error) { + return factory.NewCommitStoreReader(lggr, versionFinder, address, ec, lp, feeEstimatorConfig) } -func CloseCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address ccip.Address, ec client.Client, lp logpoller.LogPoller) error { - return factory.CloseCommitStoreReader(lggr, versionFinder, address, ec, lp) +func CloseCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address ccip.Address, ec client.Client, lp logpoller.LogPoller, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) error { + return factory.CloseCommitStoreReader(lggr, versionFinder, address, ec, lp, feeEstimatorConfig) } -func NewOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr ccip.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, registerFilters bool) (ccipdata.OffRampReader, error) { - return factory.NewOffRampReader(lggr, versionFinder, addr, destClient, lp, estimator, destMaxGasPrice, registerFilters) +func NewOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr ccip.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, registerFilters bool, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) (ccipdata.OffRampReader, error) { + return factory.NewOffRampReader(lggr, versionFinder, addr, destClient, lp, estimator, destMaxGasPrice, registerFilters, feeEstimatorConfig) } -func CloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr ccip.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int) error { - return factory.CloseOffRampReader(lggr, versionFinder, addr, destClient, lp, estimator, destMaxGasPrice) +func CloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr ccip.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) error { + return factory.CloseOffRampReader(lggr, versionFinder, addr, destClient, lp, estimator, destMaxGasPrice, feeEstimatorConfig) } func NewEvmVersionFinder() factory.EvmVersionFinder { diff --git a/core/services/ocr2/plugins/ccip/internal/cache/commit_roots_test.go b/core/services/ocr2/plugins/ccip/internal/cache/commit_roots_test.go index dc0a844349..d8289212e8 100644 --- a/core/services/ocr2/plugins/ccip/internal/cache/commit_roots_test.go +++ b/core/services/ocr2/plugins/ccip/internal/cache/commit_roots_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/require" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" @@ -18,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/cache" + ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0" ) @@ -54,7 +54,9 @@ func Test_RootsEligibleForExecution(t *testing.T) { } require.NoError(t, orm.InsertLogsWithBlock(ctx, inputLogs, logpoller.NewLogPollerBlock(utils.RandomBytes32(), 2, time.Now(), 1))) - commitStore, err := v1_2_0.NewCommitStore(logger.TestLogger(t), commitStoreAddr, nil, lp) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + + commitStore, err := v1_2_0.NewCommitStore(logger.TestLogger(t), commitStoreAddr, nil, lp, feeEstimatorConfig) require.NoError(t, err) rootsCache := cache.NewCommitRootsCache(logger.TestLogger(t), commitStore, 10*time.Hour, time.Second) @@ -162,7 +164,9 @@ func Test_RootsEligibleForExecutionWithReorgs(t *testing.T) { } require.NoError(t, orm.InsertLogsWithBlock(ctx, inputLogs, logpoller.NewLogPollerBlock(utils.RandomBytes32(), 3, time.Now(), 1))) - commitStore, err := v1_2_0.NewCommitStore(logger.TestLogger(t), commitStoreAddr, nil, lp) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + + commitStore, err := v1_2_0.NewCommitStore(logger.TestLogger(t), commitStoreAddr, nil, lp, feeEstimatorConfig) require.NoError(t, err) rootsCache := cache.NewCommitRootsCache(logger.TestLogger(t), commitStore, 10*time.Hour, time.Second) @@ -221,7 +225,9 @@ func Test_BlocksWithTheSameTimestamps(t *testing.T) { } require.NoError(t, orm.InsertLogsWithBlock(ctx, inputLogs, logpoller.NewLogPollerBlock(utils.RandomBytes32(), 2, time.Now(), 2))) - commitStore, err := v1_2_0.NewCommitStore(logger.TestLogger(t), commitStoreAddr, nil, lp) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + + commitStore, err := v1_2_0.NewCommitStore(logger.TestLogger(t), commitStoreAddr, nil, lp, feeEstimatorConfig) require.NoError(t, err) rootsCache := cache.NewCommitRootsCache(logger.TestLogger(t), commitStore, 10*time.Hour, time.Second) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go index 4c43edcc05..e9266c4fb2 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go @@ -13,7 +13,6 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/config" - cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -38,6 +37,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/factory" + ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0" ) @@ -193,15 +193,17 @@ func TestCommitStoreReaders(t *testing.T) { lm := new(rollupMocks.L1Oracle) ge.On("L1Oracle").Return(lm) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + maxGasPrice := big.NewInt(1e8) - c10r, err := factory.NewCommitStoreReader(lggr, factory.NewEvmVersionFinder(), ccipcalc.EvmAddrToGeneric(addr), ec, lp) // ge, maxGasPrice + c10r, err := factory.NewCommitStoreReader(lggr, factory.NewEvmVersionFinder(), ccipcalc.EvmAddrToGeneric(addr), ec, lp, feeEstimatorConfig) // ge, maxGasPrice require.NoError(t, err) err = c10r.SetGasEstimator(ctx, ge) require.NoError(t, err) err = c10r.SetSourceMaxGasPrice(ctx, maxGasPrice) require.NoError(t, err) assert.Equal(t, reflect.TypeOf(c10r).String(), reflect.TypeOf(&v1_0_0.CommitStore{}).String()) - c12r, err := factory.NewCommitStoreReader(lggr, factory.NewEvmVersionFinder(), ccipcalc.EvmAddrToGeneric(addr2), ec, lp) + c12r, err := factory.NewCommitStoreReader(lggr, factory.NewEvmVersionFinder(), ccipcalc.EvmAddrToGeneric(addr2), ec, lp, feeEstimatorConfig) require.NoError(t, err) err = c12r.SetGasEstimator(ctx, ge) require.NoError(t, err) @@ -412,7 +414,10 @@ func TestNewCommitStoreReader(t *testing.T) { if tc.expectedErr == "" { lp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil) } - _, err = factory.NewCommitStoreReader(logger.TestLogger(t), factory.NewEvmVersionFinder(), addr, c, lp) + + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + + _, err = factory.NewCommitStoreReader(logger.TestLogger(t), factory.NewEvmVersionFinder(), addr, c, lp, feeEstimatorConfig) if tc.expectedErr != "" { require.EqualError(t, err, tc.expectedErr) } else { diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/commit_store.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/commit_store.go index d431d2863a..c5f32ba91c 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/commit_store.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/commit_store.go @@ -21,16 +21,16 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0" ) -func NewCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address cciptypes.Address, ec client.Client, lp logpoller.LogPoller) (ccipdata.CommitStoreReader, error) { - return initOrCloseCommitStoreReader(lggr, versionFinder, address, ec, lp, false) +func NewCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address cciptypes.Address, ec client.Client, lp logpoller.LogPoller, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) (ccipdata.CommitStoreReader, error) { + return initOrCloseCommitStoreReader(lggr, versionFinder, address, ec, lp, feeEstimatorConfig, false) } -func CloseCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address cciptypes.Address, ec client.Client, lp logpoller.LogPoller) error { - _, err := initOrCloseCommitStoreReader(lggr, versionFinder, address, ec, lp, true) +func CloseCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address cciptypes.Address, ec client.Client, lp logpoller.LogPoller, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) error { + _, err := initOrCloseCommitStoreReader(lggr, versionFinder, address, ec, lp, feeEstimatorConfig, true) return err } -func initOrCloseCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address cciptypes.Address, ec client.Client, lp logpoller.LogPoller, closeReader bool) (ccipdata.CommitStoreReader, error) { +func initOrCloseCommitStoreReader(lggr logger.Logger, versionFinder VersionFinder, address cciptypes.Address, ec client.Client, lp logpoller.LogPoller, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader, closeReader bool) (ccipdata.CommitStoreReader, error) { contractType, version, err := versionFinder.TypeAndVersion(address, ec) if err != nil { return nil, errors.Wrapf(err, "unable to read type and version") @@ -57,7 +57,7 @@ func initOrCloseCommitStoreReader(lggr logger.Logger, versionFinder VersionFinde } return cs, cs.RegisterFilters() case ccipdata.V1_2_0: - cs, err := v1_2_0.NewCommitStore(lggr, evmAddr, ec, lp) + cs, err := v1_2_0.NewCommitStore(lggr, evmAddr, ec, lp, feeEstimatorConfig) if err != nil { return nil, err } @@ -66,7 +66,7 @@ func initOrCloseCommitStoreReader(lggr logger.Logger, versionFinder VersionFinde } return cs, cs.RegisterFilters() case ccipdata.V1_5_0: - cs, err := v1_5_0.NewCommitStore(lggr, evmAddr, ec, lp) + cs, err := v1_5_0.NewCommitStore(lggr, evmAddr, ec, lp, feeEstimatorConfig) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/commit_store_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/commit_store_test.go index e1b8ff929c..987b6f848e 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/commit_store_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/commit_store_test.go @@ -8,13 +8,13 @@ import ( "github.com/stretchr/testify/mock" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" mocks2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/logger" ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" + ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" ) @@ -24,14 +24,16 @@ func TestCommitStore(t *testing.T) { addr := cciptypes.Address(utils.RandomAddress().String()) lp := mocks2.NewLogPoller(t) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + lp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil) versionFinder := newMockVersionFinder(ccipconfig.CommitStore, *semver.MustParse(versionStr), nil) - _, err := NewCommitStoreReader(lggr, versionFinder, addr, nil, lp) + _, err := NewCommitStoreReader(lggr, versionFinder, addr, nil, lp, feeEstimatorConfig) assert.NoError(t, err) expFilterName := logpoller.FilterName(v1_0_0.EXEC_REPORT_ACCEPTS, addr) lp.On("UnregisterFilter", mock.Anything, expFilterName).Return(nil) - err = CloseCommitStoreReader(lggr, versionFinder, addr, nil, lp) + err = CloseCommitStoreReader(lggr, versionFinder, addr, nil, lp, feeEstimatorConfig) assert.NoError(t, err) } } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/offramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/offramp.go index c6fa63ee82..5d9b751d0b 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/offramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/offramp.go @@ -26,16 +26,16 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0" ) -func NewOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr cciptypes.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, registerFilters bool) (ccipdata.OffRampReader, error) { - return initOrCloseOffRampReader(lggr, versionFinder, addr, destClient, lp, estimator, destMaxGasPrice, false, registerFilters) +func NewOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr cciptypes.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, registerFilters bool, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) (ccipdata.OffRampReader, error) { + return initOrCloseOffRampReader(lggr, versionFinder, addr, destClient, lp, estimator, destMaxGasPrice, false, registerFilters, feeEstimatorConfig) } -func CloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr cciptypes.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int) error { - _, err := initOrCloseOffRampReader(lggr, versionFinder, addr, destClient, lp, estimator, destMaxGasPrice, true, false) +func CloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr cciptypes.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) error { + _, err := initOrCloseOffRampReader(lggr, versionFinder, addr, destClient, lp, estimator, destMaxGasPrice, true, false, feeEstimatorConfig) return err } -func initOrCloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr cciptypes.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, closeReader bool, registerFilters bool) (ccipdata.OffRampReader, error) { +func initOrCloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, addr cciptypes.Address, destClient client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, closeReader bool, registerFilters bool, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) (ccipdata.OffRampReader, error) { contractType, version, err := versionFinder.TypeAndVersion(addr, destClient) if err != nil { return nil, errors.Wrapf(err, "unable to read type and version") @@ -53,7 +53,7 @@ func initOrCloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, a switch version.String() { case ccipdata.V1_0_0, ccipdata.V1_1_0: - offRamp, err := v1_0_0.NewOffRamp(lggr, evmAddr, destClient, lp, estimator, destMaxGasPrice) + offRamp, err := v1_0_0.NewOffRamp(lggr, evmAddr, destClient, lp, estimator, destMaxGasPrice, feeEstimatorConfig) if err != nil { return nil, err } @@ -62,7 +62,7 @@ func initOrCloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, a } return offRamp, offRamp.RegisterFilters() case ccipdata.V1_2_0: - offRamp, err := v1_2_0.NewOffRamp(lggr, evmAddr, destClient, lp, estimator, destMaxGasPrice) + offRamp, err := v1_2_0.NewOffRamp(lggr, evmAddr, destClient, lp, estimator, destMaxGasPrice, feeEstimatorConfig) if err != nil { return nil, err } @@ -71,7 +71,7 @@ func initOrCloseOffRampReader(lggr logger.Logger, versionFinder VersionFinder, a } return offRamp, offRamp.RegisterFilters() case ccipdata.V1_5_0: - offRamp, err := v1_5_0.NewOffRamp(lggr, evmAddr, destClient, lp, estimator, destMaxGasPrice) + offRamp, err := v1_5_0.NewOffRamp(lggr, evmAddr, destClient, lp, estimator, destMaxGasPrice, feeEstimatorConfig) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/offramp_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/offramp_test.go index 4b9e57ecfb..c00d9e134b 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/offramp_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/factory/offramp_test.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" + ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" ) @@ -24,6 +25,8 @@ func TestOffRamp(t *testing.T) { addr := cciptypes.Address(utils.RandomAddress().String()) lp := mocks2.NewLogPoller(t) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + expFilterNames := []string{ logpoller.FilterName(v1_0_0.EXEC_EXECUTION_STATE_CHANGES, addr), logpoller.FilterName(v1_0_0.EXEC_TOKEN_POOL_ADDED, addr), @@ -32,13 +35,13 @@ func TestOffRamp(t *testing.T) { versionFinder := newMockVersionFinder(ccipconfig.EVM2EVMOffRamp, *semver.MustParse(versionStr), nil) lp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil).Times(len(expFilterNames)) - _, err := NewOffRampReader(lggr, versionFinder, addr, nil, lp, nil, nil, true) + _, err := NewOffRampReader(lggr, versionFinder, addr, nil, lp, nil, nil, true, feeEstimatorConfig) assert.NoError(t, err) for _, f := range expFilterNames { lp.On("UnregisterFilter", mock.Anything, f).Return(nil) } - err = CloseOffRampReader(lggr, versionFinder, addr, nil, lp, nil, nil) + err = CloseOffRampReader(lggr, versionFinder, addr, nil, lp, nil, nil, feeEstimatorConfig) assert.NoError(t, err) } } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/fee_estimator_config.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/fee_estimator_config.go new file mode 100644 index 0000000000..a1a9370528 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/fee_estimator_config.go @@ -0,0 +1,9 @@ +package ccipdata + +import ( + "context" +) + +type FeeEstimatorConfigReader interface { + GetDataAvailabilityConfig(ctx context.Context) (destDAOverheadGas, destGasPerDAByte, destDAMultiplierBps int64, err error) +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks/fee_estimator_config_mock.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks/fee_estimator_config_mock.go new file mode 100644 index 0000000000..b19f4cd882 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks/fee_estimator_config_mock.go @@ -0,0 +1,106 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// FeeEstimatorConfigReader is an autogenerated mock type for the FeeEstimatorConfigReader type +type FeeEstimatorConfigReader struct { + mock.Mock +} + +type FeeEstimatorConfigReader_Expecter struct { + mock *mock.Mock +} + +func (_m *FeeEstimatorConfigReader) EXPECT() *FeeEstimatorConfigReader_Expecter { + return &FeeEstimatorConfigReader_Expecter{mock: &_m.Mock} +} + +// GetDataAvailabilityConfig provides a mock function with given fields: ctx +func (_m *FeeEstimatorConfigReader) GetDataAvailabilityConfig(ctx context.Context) (int64, int64, int64, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetDataAvailabilityConfig") + } + + var r0 int64 + var r1 int64 + var r2 int64 + var r3 error + if rf, ok := ret.Get(0).(func(context.Context) (int64, int64, int64, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) int64); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(int64) + } + + if rf, ok := ret.Get(1).(func(context.Context) int64); ok { + r1 = rf(ctx) + } else { + r1 = ret.Get(1).(int64) + } + + if rf, ok := ret.Get(2).(func(context.Context) int64); ok { + r2 = rf(ctx) + } else { + r2 = ret.Get(2).(int64) + } + + if rf, ok := ret.Get(3).(func(context.Context) error); ok { + r3 = rf(ctx) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + +// FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetDataAvailabilityConfig' +type FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call struct { + *mock.Call +} + +// GetDataAvailabilityConfig is a helper method to define mock.On call +// - ctx context.Context +func (_e *FeeEstimatorConfigReader_Expecter) GetDataAvailabilityConfig(ctx interface{}) *FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call { + return &FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call{Call: _e.mock.On("GetDataAvailabilityConfig", ctx)} +} + +func (_c *FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call) Run(run func(ctx context.Context)) *FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call) Return(destDAOverheadGas int64, destGasPerDAByte int64, destDAMultiplierBps int64, err error) *FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call { + _c.Call.Return(destDAOverheadGas, destGasPerDAByte, destDAMultiplierBps, err) + return _c +} + +func (_c *FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call) RunAndReturn(run func(context.Context) (int64, int64, int64, error)) *FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call { + _c.Call.Return(run) + return _c +} + +// NewFeeEstimatorConfigReader creates a new instance of FeeEstimatorConfigReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewFeeEstimatorConfigReader(t interface { + mock.TestingT + Cleanup(func()) +}) *FeeEstimatorConfigReader { + mock := &FeeEstimatorConfigReader{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/offramp_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/offramp_reader_test.go index f5711422bd..df405a5a61 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/offramp_reader_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/offramp_reader_test.go @@ -13,7 +13,6 @@ import ( "github.com/stretchr/testify/require" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmclientmocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" @@ -32,6 +31,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/factory" + ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0" ) @@ -194,8 +194,10 @@ func setupOffRampReaderTH(t *testing.T, version string) offRampReaderTH { require.Fail(t, "Unknown version: ", version) } + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + // Create the version-specific reader. - reader, err := factory.NewOffRampReader(log, factory.NewEvmVersionFinder(), ccipcalc.EvmAddrToGeneric(offRampAddress), bc, lp, nil, nil, true) + reader, err := factory.NewOffRampReader(log, factory.NewEvmVersionFinder(), ccipcalc.EvmAddrToGeneric(offRampAddress), bc, lp, nil, nil, true, feeEstimatorConfig) require.NoError(t, err) addr, err := reader.Address(ctx) require.NoError(t, err) @@ -401,11 +403,14 @@ func TestNewOffRampReader(t *testing.T) { b, err := utils.ABIEncode(`[{"type":"string"}]`, tc.typeAndVersion) require.NoError(t, err) c := evmclientmocks.NewClient(t) + + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + c.On("CallContract", mock.Anything, mock.Anything, mock.Anything).Return(b, nil) addr := ccipcalc.EvmAddrToGeneric(utils.RandomAddress()) lp := lpmocks.NewLogPoller(t) lp.On("RegisterFilter", mock.Anything, mock.Anything).Return(nil).Maybe() - _, err = factory.NewOffRampReader(logger.TestLogger(t), factory.NewEvmVersionFinder(), addr, c, lp, nil, nil, true) + _, err = factory.NewOffRampReader(logger.TestLogger(t), factory.NewEvmVersionFinder(), addr, c, lp, nil, nil, true, feeEstimatorConfig) if tc.expectedErr != "" { assert.EqualError(t, err, tc.expectedErr) } else { diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp.go index 137cbaf451..b5625c59d0 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp.go @@ -156,6 +156,7 @@ type OffRamp struct { eventSig common.Hash cachedOffRampTokens cache.AutoSync[cciptypes.OffRampTokens] sourceToDestTokensCache sync.Map + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader // Dynamic config // configMu guards all the dynamic config fields. @@ -627,7 +628,7 @@ func (o *OffRamp) RegisterFilters() error { return logpollerutil.RegisterLpFilters(o.lp, o.filters) } -func NewOffRamp(lggr logger.Logger, addr common.Address, ec client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int) (*OffRamp, error) { +func NewOffRamp(lggr logger.Logger, addr common.Address, ec client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) (*OffRamp, error) { offRamp, err := evm_2_evm_offramp_1_0_0.NewEVM2EVMOffRamp(addr, ec) if err != nil { return nil, err @@ -682,8 +683,9 @@ func NewOffRamp(lggr logger.Logger, addr common.Address, ec client.Client, lp lo offRamp.Address(), ), // values set on the fly after ChangeConfig is called - gasPriceEstimator: prices.ExecGasPriceEstimator{}, - offchainConfig: cciptypes.ExecOffchainConfig{}, - onchainConfig: cciptypes.ExecOnchainConfig{}, + gasPriceEstimator: prices.ExecGasPriceEstimator{}, + offchainConfig: cciptypes.ExecOffchainConfig{}, + onchainConfig: cciptypes.ExecOnchainConfig{}, + feeEstimatorConfig: feeEstimatorConfig, }, nil } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp_reader_test.go index d834b792ce..455c1dbcb8 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp_reader_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp_reader_test.go @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0" ) @@ -25,7 +26,9 @@ func TestExecutionReportEncodingV100(t *testing.T) { ProofFlagBits: big.NewInt(133), } - offRamp, err := v1_0_0.NewOffRamp(logger.TestLogger(t), utils.RandomAddress(), nil, lpmocks.NewLogPoller(t), nil, nil) + feeEstimatorConfig := mocks.NewFeeEstimatorConfigReader(t) + + offRamp, err := v1_0_0.NewOffRamp(logger.TestLogger(t), utils.RandomAddress(), nil, lpmocks.NewLogPoller(t), nil, nil, feeEstimatorConfig) require.NoError(t, err) ctx := testutils.Context(t) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp_reader_unit_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp_reader_unit_test.go index f8b1dc4e61..6cde3753b7 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp_reader_unit_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/offramp_reader_unit_test.go @@ -6,16 +6,12 @@ import ( "slices" "testing" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/rpclib" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/rpclib/rpclibmocks" - "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - evmclimocks "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" @@ -27,6 +23,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/cache" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" + ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/rpclib" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/rpclib/rpclibmocks" ) func TestOffRampGetDestinationTokensFromSourceTokens(t *testing.T) { @@ -190,13 +189,15 @@ func Test_LogsAreProperlyMarkedAsFinalized(t *testing.T) { t.Run(tt.name, func(t *testing.T) { offrampAddress := utils.RandomAddress() + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + lp := mocks.NewLogPoller(t) lp.On("LatestBlock", mock.Anything). Return(logpoller.LogPollerBlock{FinalizedBlockNumber: int64(tt.lastFinalizedBlock)}, nil) lp.On("IndexedLogsTopicRange", mock.Anything, ExecutionStateChangedEvent, offrampAddress, 1, logpoller.EvmWord(minSeqNr), logpoller.EvmWord(maxSeqNr), evmtypes.Confirmations(0)). Return(inputLogs, nil) - offRamp, err := NewOffRamp(logger.TestLogger(t), offrampAddress, evmclimocks.NewClient(t), lp, nil, nil) + offRamp, err := NewOffRamp(logger.TestLogger(t), offrampAddress, evmclimocks.NewClient(t), lp, nil, nil, feeEstimatorConfig) require.NoError(t, err) logs, err := offRamp.GetExecutionStateChangesBetweenSeqNums(testutils.Context(t), minSeqNr, maxSeqNr, 0) require.NoError(t, err) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go index 6737abe64c..d6f3094af7 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go @@ -30,16 +30,16 @@ const ( var _ ccipdata.OnRampReader = &OnRamp{} type OnRamp struct { - address common.Address - onRamp *evm_2_evm_onramp_1_0_0.EVM2EVMOnRamp - lp logpoller.LogPoller - lggr logger.Logger - client client.Client - leafHasher ccipdata.LeafHasherInterface[[32]byte] - sendRequestedEventSig common.Hash - sendRequestedSeqNumberWord int - filters []logpoller.Filter - cachedSourcePriceRegistryAddress cache.AutoSync[cciptypes.Address] + address common.Address + onRamp *evm_2_evm_onramp_1_0_0.EVM2EVMOnRamp + lp logpoller.LogPoller + lggr logger.Logger + client client.Client + leafHasher ccipdata.LeafHasherInterface[[32]byte] + sendRequestedEventSig common.Hash + sendRequestedSeqNumberWord int + filters []logpoller.Filter + cachedOnRampDynamicConfig cache.AutoSync[cciptypes.OnRampDynamicConfig] // Static config can be cached, because it's never expected to change. // The only way to change that is through the contract's constructor (redeployment) cachedStaticConfig cache.OnceCtxFunction[evm_2_evm_onramp_1_0_0.EVM2EVMOnRampStaticConfig] @@ -90,7 +90,7 @@ func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAd // offset || sourceChainID || seqNum || ... sendRequestedSeqNumberWord: 2, sendRequestedEventSig: eventSig, - cachedSourcePriceRegistryAddress: cache.NewLogpollerEventsBased[cciptypes.Address]( + cachedOnRampDynamicConfig: cache.NewLogpollerEventsBased[cciptypes.OnRampDynamicConfig]( sourceLP, []common.Hash{configSetEventSig}, onRampAddress, @@ -104,38 +104,38 @@ func (o *OnRamp) Address(context.Context) (cciptypes.Address, error) { return cciptypes.Address(o.onRamp.Address().String()), nil } -func (o *OnRamp) GetDynamicConfig(context.Context) (cciptypes.OnRampDynamicConfig, error) { - if o.onRamp == nil { - return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("onramp not initialized") - } - legacyDynamicConfig, err := o.onRamp.GetDynamicConfig(nil) - if err != nil { - return cciptypes.OnRampDynamicConfig{}, err - } - return cciptypes.OnRampDynamicConfig{ - Router: cciptypes.Address(legacyDynamicConfig.Router.String()), - MaxNumberOfTokensPerMsg: legacyDynamicConfig.MaxTokensLength, - DestGasOverhead: 0, - DestGasPerPayloadByte: 0, - DestDataAvailabilityOverheadGas: 0, - DestGasPerDataAvailabilityByte: 0, - DestDataAvailabilityMultiplierBps: 0, - PriceRegistry: cciptypes.Address(legacyDynamicConfig.PriceRegistry.String()), - MaxDataBytes: legacyDynamicConfig.MaxDataSize, - MaxPerMsgGasLimit: uint32(legacyDynamicConfig.MaxGasLimit), - }, nil -} - -func (o *OnRamp) SourcePriceRegistryAddress(ctx context.Context) (cciptypes.Address, error) { - return o.cachedSourcePriceRegistryAddress.Get(ctx, func(ctx context.Context) (cciptypes.Address, error) { - c, err := o.GetDynamicConfig(ctx) +func (o *OnRamp) GetDynamicConfig(ctx context.Context) (cciptypes.OnRampDynamicConfig, error) { + return o.cachedOnRampDynamicConfig.Get(ctx, func(ctx context.Context) (cciptypes.OnRampDynamicConfig, error) { + if o.onRamp == nil { + return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("onramp not initialized") + } + legacyDynamicConfig, err := o.onRamp.GetDynamicConfig(&bind.CallOpts{Context: ctx}) if err != nil { - return "", err + return cciptypes.OnRampDynamicConfig{}, err } - return c.PriceRegistry, nil + return cciptypes.OnRampDynamicConfig{ + Router: cciptypes.Address(legacyDynamicConfig.Router.String()), + MaxNumberOfTokensPerMsg: legacyDynamicConfig.MaxTokensLength, + DestGasOverhead: 0, + DestGasPerPayloadByte: 0, + DestDataAvailabilityOverheadGas: 0, + DestGasPerDataAvailabilityByte: 0, + DestDataAvailabilityMultiplierBps: 0, + PriceRegistry: cciptypes.Address(legacyDynamicConfig.PriceRegistry.String()), + MaxDataBytes: legacyDynamicConfig.MaxDataSize, + MaxPerMsgGasLimit: uint32(legacyDynamicConfig.MaxGasLimit), + }, nil }) } +func (o *OnRamp) SourcePriceRegistryAddress(ctx context.Context) (cciptypes.Address, error) { + c, err := o.GetDynamicConfig(ctx) + if err != nil { + return "", err + } + return c.PriceRegistry, nil +} + func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, seqNumMax uint64, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { logs, err := o.lp.LogsDataWordRange( ctx, @@ -165,8 +165,8 @@ func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, s return res, nil } -func (o *OnRamp) RouterAddress(context.Context) (cciptypes.Address, error) { - config, err := o.onRamp.GetDynamicConfig(nil) +func (o *OnRamp) RouterAddress(ctx context.Context) (cciptypes.Address, error) { + config, err := o.onRamp.GetDynamicConfig(&bind.CallOpts{Context: ctx}) if err != nil { return "", err } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store.go index 7612e54419..ecc8acb576 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store.go @@ -49,9 +49,10 @@ type CommitStore struct { commitReportArgs abi.Arguments // Dynamic config - configMu sync.RWMutex - gasPriceEstimator *prices.DAGasPriceEstimator - offchainConfig cciptypes.CommitOffchainConfig + configMu sync.RWMutex + gasPriceEstimator *prices.DAGasPriceEstimator + offchainConfig cciptypes.CommitOffchainConfig + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader } func (c *CommitStore) GetCommitStoreStaticConfig(ctx context.Context) (cciptypes.CommitStoreStaticConfig, error) { @@ -255,6 +256,7 @@ func (c *CommitStore) ChangeConfig(_ context.Context, onchainConfig []byte, offc c.sourceMaxGasPrice, int64(offchainConfigParsed.ExecGasPriceDeviationPPB), int64(offchainConfigParsed.DAGasPriceDeviationPPB), + c.feeEstimatorConfig, ) c.offchainConfig = ccipdata.NewCommitOffchainConfig( offchainConfigParsed.ExecGasPriceDeviationPPB, @@ -430,7 +432,7 @@ func (c *CommitStore) RegisterFilters() error { return logpollerutil.RegisterLpFilters(c.lp, c.filters) } -func NewCommitStore(lggr logger.Logger, addr common.Address, ec client.Client, lp logpoller.LogPoller) (*CommitStore, error) { +func NewCommitStore(lggr logger.Logger, addr common.Address, ec client.Client, lp logpoller.LogPoller, feeEstimatorConfig ccipdata.FeeEstimatorConfigReader) (*CommitStore, error) { commitStore, err := commit_store_1_2_0.NewCommitStore(addr, ec) if err != nil { return nil, err @@ -463,7 +465,8 @@ func NewCommitStore(lggr logger.Logger, addr common.Address, ec client.Client, l configMu: sync.RWMutex{}, // The fields below are initially empty and set on ChangeConfig method - offchainConfig: cciptypes.CommitOffchainConfig{}, - gasPriceEstimator: nil, + offchainConfig: cciptypes.CommitOffchainConfig{}, + gasPriceEstimator: nil, + feeEstimatorConfig: feeEstimatorConfig, }, nil } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store_test.go index 8b29309633..4307be0353 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/commit_store_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/config" - cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller/mocks" @@ -18,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" ) func TestCommitReportEncoding(t *testing.T) { @@ -48,7 +48,9 @@ func TestCommitReportEncoding(t *testing.T) { Interval: cciptypes.CommitStoreInterval{Min: 1, Max: 10}, } - c, err := NewCommitStore(logger.TestLogger(t), utils.RandomAddress(), nil, mocks.NewLogPoller(t)) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + + c, err := NewCommitStore(logger.TestLogger(t), utils.RandomAddress(), nil, mocks.NewLogPoller(t), feeEstimatorConfig) assert.NoError(t, err) encodedReport, err := c.EncodeCommitReport(ctx, report) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp.go index fa00894b38..f853adfb6f 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp.go @@ -123,7 +123,8 @@ func (c JSONExecOffchainConfig) Validate() error { // OffRamp In 1.2 we have a different estimator impl type OffRamp struct { *v1_0_0.OffRamp - offRampV120 evm_2_evm_offramp_1_2_0.EVM2EVMOffRampInterface + offRampV120 evm_2_evm_offramp_1_2_0.EVM2EVMOffRampInterface + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader } func (o *OffRamp) CurrentRateLimiterState(ctx context.Context) (cciptypes.TokenBucketRateLimit, error) { @@ -180,7 +181,7 @@ func (o *OffRamp) ChangeConfig(ctx context.Context, onchainConfigBytes []byte, o PermissionLessExecutionThresholdSeconds: time.Second * time.Duration(onchainConfigParsed.PermissionLessExecutionThresholdSeconds), Router: cciptypes.Address(onchainConfigParsed.Router.String()), } - priceEstimator := prices.NewDAGasPriceEstimator(o.Estimator, o.DestMaxGasPrice, 0, 0) + priceEstimator := prices.NewDAGasPriceEstimator(o.Estimator, o.DestMaxGasPrice, 0, 0, o.feeEstimatorConfig) o.UpdateDynamicConfig(onchainConfig, offchainConfig, priceEstimator) @@ -320,8 +321,16 @@ func (o *OffRamp) DecodeExecutionReport(ctx context.Context, report []byte) (cci return DecodeExecReport(ctx, o.ExecutionReportArgs, report) } -func NewOffRamp(lggr logger.Logger, addr common.Address, ec client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int) (*OffRamp, error) { - v100, err := v1_0_0.NewOffRamp(lggr, addr, ec, lp, estimator, destMaxGasPrice) +func NewOffRamp( + lggr logger.Logger, + addr common.Address, + ec client.Client, + lp logpoller.LogPoller, + estimator gas.EvmFeeEstimator, + destMaxGasPrice *big.Int, + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader, +) (*OffRamp, error) { + v100, err := v1_0_0.NewOffRamp(lggr, addr, ec, lp, estimator, destMaxGasPrice, feeEstimatorConfig) if err != nil { return nil, err } @@ -334,7 +343,8 @@ func NewOffRamp(lggr logger.Logger, addr common.Address, ec client.Client, lp lo v100.ExecutionReportArgs = abihelpers.MustGetMethodInputs("manuallyExecute", abiOffRamp)[:1] return &OffRamp{ - OffRamp: v100, - offRampV120: offRamp, + OffRamp: v100, + offRampV120: offRamp, + feeEstimatorConfig: feeEstimatorConfig, }, nil } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp_reader_test.go index f87fc8842f..c298349261 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp_reader_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/offramp_reader_test.go @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0" ) @@ -25,7 +26,9 @@ func TestExecutionReportEncodingV120(t *testing.T) { ProofFlagBits: big.NewInt(133), } - offRamp, err := v1_2_0.NewOffRamp(logger.TestLogger(t), utils.RandomAddress(), nil, lpmocks.NewLogPoller(t), nil, nil) + feeEstimatorConfig := mocks.NewFeeEstimatorConfigReader(t) + + offRamp, err := v1_2_0.NewOffRamp(logger.TestLogger(t), utils.RandomAddress(), nil, lpmocks.NewLogPoller(t), nil, nil, feeEstimatorConfig) require.NoError(t, err) ctx := testutils.Context(t) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp.go index 2939682347..2de2b104c9 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_2_0/onramp.go @@ -49,16 +49,16 @@ var _ ccipdata.OnRampReader = &OnRamp{} // Significant change in 1.2: // - CCIPSendRequested event signature has changed type OnRamp struct { - onRamp *evm_2_evm_onramp_1_2_0.EVM2EVMOnRamp - address common.Address - lggr logger.Logger - lp logpoller.LogPoller - leafHasher ccipdata.LeafHasherInterface[[32]byte] - client client.Client - sendRequestedEventSig common.Hash - sendRequestedSeqNumberWord int - filters []logpoller.Filter - cachedSourcePriceRegistryAddress cache.AutoSync[cciptypes.Address] + onRamp *evm_2_evm_onramp_1_2_0.EVM2EVMOnRamp + address common.Address + lggr logger.Logger + lp logpoller.LogPoller + leafHasher ccipdata.LeafHasherInterface[[32]byte] + client client.Client + sendRequestedEventSig common.Hash + sendRequestedSeqNumberWord int + filters []logpoller.Filter + cachedOnRampDynamicConfig cache.AutoSync[cciptypes.OnRampDynamicConfig] // Static config can be cached, because it's never expected to change. // The only way to change that is through the contract's constructor (redeployment) cachedStaticConfig cache.OnceCtxFunction[evm_2_evm_onramp_1_2_0.EVM2EVMOnRampStaticConfig] @@ -107,7 +107,7 @@ func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAd address: onRampAddress, sendRequestedSeqNumberWord: CCIPSendRequestSeqNumIndex, sendRequestedEventSig: CCIPSendRequestEventSig, - cachedSourcePriceRegistryAddress: cache.NewLogpollerEventsBased[cciptypes.Address]( + cachedOnRampDynamicConfig: cache.NewLogpollerEventsBased[cciptypes.OnRampDynamicConfig]( sourceLP, []common.Hash{ConfigSetEventSig}, onRampAddress, @@ -121,38 +121,39 @@ func (o *OnRamp) Address(context.Context) (cciptypes.Address, error) { return cciptypes.Address(o.onRamp.Address().String()), nil } -func (o *OnRamp) GetDynamicConfig(context.Context) (cciptypes.OnRampDynamicConfig, error) { - if o.onRamp == nil { - return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("onramp not initialized") - } - config, err := o.onRamp.GetDynamicConfig(&bind.CallOpts{}) - if err != nil { - return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("get dynamic config v1.2: %w", err) - } - return cciptypes.OnRampDynamicConfig{ - Router: cciptypes.Address(config.Router.String()), - MaxNumberOfTokensPerMsg: config.MaxNumberOfTokensPerMsg, - DestGasOverhead: config.DestGasOverhead, - DestGasPerPayloadByte: config.DestGasPerPayloadByte, - DestDataAvailabilityOverheadGas: config.DestDataAvailabilityOverheadGas, - DestGasPerDataAvailabilityByte: config.DestGasPerDataAvailabilityByte, - DestDataAvailabilityMultiplierBps: config.DestDataAvailabilityMultiplierBps, - PriceRegistry: cciptypes.Address(config.PriceRegistry.String()), - MaxDataBytes: config.MaxDataBytes, - MaxPerMsgGasLimit: config.MaxPerMsgGasLimit, - }, nil -} - -func (o *OnRamp) SourcePriceRegistryAddress(ctx context.Context) (cciptypes.Address, error) { - return o.cachedSourcePriceRegistryAddress.Get(ctx, func(ctx context.Context) (cciptypes.Address, error) { - c, err := o.GetDynamicConfig(ctx) +func (o *OnRamp) GetDynamicConfig(ctx context.Context) (cciptypes.OnRampDynamicConfig, error) { + return o.cachedOnRampDynamicConfig.Get(ctx, func(ctx context.Context) (cciptypes.OnRampDynamicConfig, error) { + if o.onRamp == nil { + return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("onramp not initialized") + } + config, err := o.onRamp.GetDynamicConfig(&bind.CallOpts{Context: ctx}) if err != nil { - return "", err + return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("get dynamic config v1.2: %w", err) } - return c.PriceRegistry, nil + + return cciptypes.OnRampDynamicConfig{ + Router: cciptypes.Address(config.Router.String()), + MaxNumberOfTokensPerMsg: config.MaxNumberOfTokensPerMsg, + DestGasOverhead: config.DestGasOverhead, + DestGasPerPayloadByte: config.DestGasPerPayloadByte, + DestDataAvailabilityOverheadGas: config.DestDataAvailabilityOverheadGas, + DestGasPerDataAvailabilityByte: config.DestGasPerDataAvailabilityByte, + DestDataAvailabilityMultiplierBps: config.DestDataAvailabilityMultiplierBps, + PriceRegistry: cciptypes.Address(config.PriceRegistry.String()), + MaxDataBytes: config.MaxDataBytes, + MaxPerMsgGasLimit: config.MaxPerMsgGasLimit, + }, nil }) } +func (o *OnRamp) SourcePriceRegistryAddress(ctx context.Context) (cciptypes.Address, error) { + c, err := o.GetDynamicConfig(ctx) + if err != nil { + return "", err + } + return c.PriceRegistry, nil +} + func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, seqNumMax uint64, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { logs, err := o.lp.LogsDataWordRange( ctx, diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/commit_store.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/commit_store.go index 3bb582f3a2..d5545174cb 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/commit_store.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/commit_store.go @@ -3,6 +3,8 @@ package v1_5_0 import ( "context" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -41,8 +43,14 @@ func (c *CommitStore) IsDown(ctx context.Context) (bool, error) { return !unPausedAndNotCursed, nil } -func NewCommitStore(lggr logger.Logger, addr common.Address, ec client.Client, lp logpoller.LogPoller) (*CommitStore, error) { - v120, err := v1_2_0.NewCommitStore(lggr, addr, ec, lp) +func NewCommitStore( + lggr logger.Logger, + addr common.Address, + ec client.Client, + lp logpoller.LogPoller, + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader, +) (*CommitStore, error) { + v120, err := v1_2_0.NewCommitStore(lggr, addr, ec, lp, feeEstimatorConfig) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/offramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/offramp.go index 15f52bd713..11e7be1a55 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/offramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/offramp.go @@ -70,6 +70,7 @@ type OffRamp struct { *v1_2_0.OffRamp offRampV150 evm_2_evm_offramp.EVM2EVMOffRampInterface cachedRateLimitTokens cache.AutoSync[cciptypes.OffRampTokens] + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader } // GetTokens Returns no data as the offRamps no longer have this information. @@ -155,7 +156,7 @@ func (o *OffRamp) ChangeConfig(ctx context.Context, onchainConfigBytes []byte, o PermissionLessExecutionThresholdSeconds: time.Second * time.Duration(onchainConfigParsed.PermissionLessExecutionThresholdSeconds), Router: cciptypes.Address(onchainConfigParsed.Router.String()), } - priceEstimator := prices.NewDAGasPriceEstimator(o.Estimator, o.DestMaxGasPrice, 0, 0) + priceEstimator := prices.NewDAGasPriceEstimator(o.Estimator, o.DestMaxGasPrice, 0, 0, o.feeEstimatorConfig) o.UpdateDynamicConfig(onchainConfig, offchainConfig, priceEstimator) @@ -166,8 +167,16 @@ func (o *OffRamp) ChangeConfig(ctx context.Context, onchainConfigBytes []byte, o cciptypes.Address(destWrappedNative.String()), nil } -func NewOffRamp(lggr logger.Logger, addr common.Address, ec client.Client, lp logpoller.LogPoller, estimator gas.EvmFeeEstimator, destMaxGasPrice *big.Int) (*OffRamp, error) { - v120, err := v1_2_0.NewOffRamp(lggr, addr, ec, lp, estimator, destMaxGasPrice) +func NewOffRamp( + lggr logger.Logger, + addr common.Address, + ec client.Client, + lp logpoller.LogPoller, + estimator gas.EvmFeeEstimator, + destMaxGasPrice *big.Int, + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader, +) (*OffRamp, error) { + v120, err := v1_2_0.NewOffRamp(lggr, addr, ec, lp, estimator, destMaxGasPrice, feeEstimatorConfig) if err != nil { return nil, err } @@ -180,8 +189,9 @@ func NewOffRamp(lggr logger.Logger, addr common.Address, ec client.Client, lp lo v120.ExecutionReportArgs = abihelpers.MustGetMethodInputs("manuallyExecute", abiOffRamp)[:1] return &OffRamp{ - OffRamp: v120, - offRampV150: offRamp, + feeEstimatorConfig: feeEstimatorConfig, + OffRamp: v120, + offRampV150: offRamp, cachedRateLimitTokens: cache.NewLogpollerEventsBased[cciptypes.OffRampTokens]( lp, []common.Hash{RateLimitTokenAddedEvent, RateLimitTokenRemovedEvent}, diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go index 354a5defdd..5a9377858d 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go @@ -50,17 +50,17 @@ func init() { var _ ccipdata.OnRampReader = &OnRamp{} type OnRamp struct { - onRamp *evm_2_evm_onramp.EVM2EVMOnRamp - address common.Address - destChainSelectorBytes [16]byte - lggr logger.Logger - lp logpoller.LogPoller - leafHasher ccipdata.LeafHasherInterface[[32]byte] - client client.Client - sendRequestedEventSig common.Hash - sendRequestedSeqNumberWord int - filters []logpoller.Filter - cachedSourcePriceRegistryAddress cache.AutoSync[cciptypes.Address] + onRamp *evm_2_evm_onramp.EVM2EVMOnRamp + address common.Address + destChainSelectorBytes [16]byte + lggr logger.Logger + lp logpoller.LogPoller + leafHasher ccipdata.LeafHasherInterface[[32]byte] + client client.Client + sendRequestedEventSig common.Hash + sendRequestedSeqNumberWord int + filters []logpoller.Filter + cachedOnRampDynamicConfig cache.AutoSync[cciptypes.OnRampDynamicConfig] // Static config can be cached, because it's never expected to change. // The only way to change that is through the contract's constructor (redeployment) cachedStaticConfig cache.OnceCtxFunction[evm_2_evm_onramp.EVM2EVMOnRampStaticConfig] @@ -112,7 +112,7 @@ func NewOnRamp(lggr logger.Logger, sourceSelector, destSelector uint64, onRampAd address: onRampAddress, sendRequestedSeqNumberWord: CCIPSendRequestSeqNumIndex, sendRequestedEventSig: CCIPSendRequestEventSig, - cachedSourcePriceRegistryAddress: cache.NewLogpollerEventsBased[cciptypes.Address]( + cachedOnRampDynamicConfig: cache.NewLogpollerEventsBased[cciptypes.OnRampDynamicConfig]( sourceLP, []common.Hash{ConfigSetEventSig}, onRampAddress, @@ -126,38 +126,39 @@ func (o *OnRamp) Address(context.Context) (cciptypes.Address, error) { return ccipcalc.EvmAddrToGeneric(o.onRamp.Address()), nil } -func (o *OnRamp) GetDynamicConfig(context.Context) (cciptypes.OnRampDynamicConfig, error) { - if o.onRamp == nil { - return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("onramp not initialized") - } - config, err := o.onRamp.GetDynamicConfig(&bind.CallOpts{}) - if err != nil { - return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("get dynamic config v1.5: %w", err) - } - return cciptypes.OnRampDynamicConfig{ - Router: ccipcalc.EvmAddrToGeneric(config.Router), - MaxNumberOfTokensPerMsg: config.MaxNumberOfTokensPerMsg, - DestGasOverhead: config.DestGasOverhead, - DestGasPerPayloadByte: config.DestGasPerPayloadByte, - DestDataAvailabilityOverheadGas: config.DestDataAvailabilityOverheadGas, - DestGasPerDataAvailabilityByte: config.DestGasPerDataAvailabilityByte, - DestDataAvailabilityMultiplierBps: config.DestDataAvailabilityMultiplierBps, - PriceRegistry: ccipcalc.EvmAddrToGeneric(config.PriceRegistry), - MaxDataBytes: config.MaxDataBytes, - MaxPerMsgGasLimit: config.MaxPerMsgGasLimit, - }, nil -} - -func (o *OnRamp) SourcePriceRegistryAddress(ctx context.Context) (cciptypes.Address, error) { - return o.cachedSourcePriceRegistryAddress.Get(ctx, func(ctx context.Context) (cciptypes.Address, error) { - c, err := o.GetDynamicConfig(ctx) +func (o *OnRamp) GetDynamicConfig(ctx context.Context) (cciptypes.OnRampDynamicConfig, error) { + return o.cachedOnRampDynamicConfig.Get(ctx, func(ctx context.Context) (cciptypes.OnRampDynamicConfig, error) { + if o.onRamp == nil { + return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("onramp not initialized") + } + config, err := o.onRamp.GetDynamicConfig(&bind.CallOpts{}) if err != nil { - return "", err + return cciptypes.OnRampDynamicConfig{}, fmt.Errorf("get dynamic config v1.5: %w", err) } - return c.PriceRegistry, nil + + return cciptypes.OnRampDynamicConfig{ + Router: ccipcalc.EvmAddrToGeneric(config.Router), + MaxNumberOfTokensPerMsg: config.MaxNumberOfTokensPerMsg, + DestGasOverhead: config.DestGasOverhead, + DestGasPerPayloadByte: config.DestGasPerPayloadByte, + DestDataAvailabilityOverheadGas: config.DestDataAvailabilityOverheadGas, + DestGasPerDataAvailabilityByte: config.DestGasPerDataAvailabilityByte, + DestDataAvailabilityMultiplierBps: config.DestDataAvailabilityMultiplierBps, + PriceRegistry: ccipcalc.EvmAddrToGeneric(config.PriceRegistry), + MaxDataBytes: config.MaxDataBytes, + MaxPerMsgGasLimit: config.MaxPerMsgGasLimit, + }, nil }) } +func (o *OnRamp) SourcePriceRegistryAddress(ctx context.Context) (cciptypes.Address, error) { + c, err := o.GetDynamicConfig(ctx) + if err != nil { + return "", err + } + return c.PriceRegistry, nil +} + func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, seqNumMax uint64, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { logs, err := o.lp.LogsDataWordRange( ctx, diff --git a/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go b/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go index 7c75b9bdd9..b27494ad60 100644 --- a/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go +++ b/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go @@ -5,6 +5,8 @@ import ( "fmt" "math/big" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" @@ -14,11 +16,9 @@ import ( type DAGasPriceEstimator struct { execEstimator GasPriceEstimator l1Oracle rollups.L1Oracle + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader priceEncodingLength uint daDeviationPPB int64 - daOverheadGas int64 - gasPerDAByte int64 - daMultiplier int64 } func NewDAGasPriceEstimator( @@ -26,12 +26,14 @@ func NewDAGasPriceEstimator( maxGasPrice *big.Int, deviationPPB int64, daDeviationPPB int64, + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader, // DA Config Cache updates in the onRamp reader and shares the state ) *DAGasPriceEstimator { return &DAGasPriceEstimator{ execEstimator: NewExecGasPriceEstimator(estimator, maxGasPrice, deviationPPB), l1Oracle: estimator.L1Oracle(), priceEncodingLength: daGasPriceEncodingLength, daDeviationPPB: daDeviationPPB, + feeEstimatorConfig: feeEstimatorConfig, } } @@ -141,7 +143,10 @@ func (g DAGasPriceEstimator) EstimateMsgCostUSD(p *big.Int, wrappedNativePrice * // If there is data availability price component, then include data availability cost in fee estimation if daGasPrice.Cmp(big.NewInt(0)) > 0 { - daGasCostUSD := g.estimateDACostUSD(daGasPrice, wrappedNativePrice, msg) + daGasCostUSD, err := g.estimateDACostUSD(daGasPrice, wrappedNativePrice, msg) + if err != nil { + return nil, err + } execCostUSD = new(big.Int).Add(daGasCostUSD, execCostUSD) } return execCostUSD, nil @@ -160,17 +165,22 @@ func (g DAGasPriceEstimator) parseEncodedGasPrice(p *big.Int) (*big.Int, *big.In return daGasPrice, execGasPrice, nil } -func (g DAGasPriceEstimator) estimateDACostUSD(daGasPrice *big.Int, wrappedNativePrice *big.Int, msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta) *big.Int { +func (g DAGasPriceEstimator) estimateDACostUSD(daGasPrice *big.Int, wrappedNativePrice *big.Int, msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta) (*big.Int, error) { var sourceTokenDataLen int for _, tokenData := range msg.SourceTokenData { sourceTokenDataLen += len(tokenData) } + daOverheadGas, gasPerDAByte, daMultiplier, err := g.feeEstimatorConfig.GetDataAvailabilityConfig(context.Background()) + if err != nil { + return nil, err + } + dataLen := evmMessageFixedBytes + len(msg.Data) + len(msg.TokenAmounts)*evmMessageBytesPerToken + sourceTokenDataLen - dataGas := big.NewInt(int64(dataLen)*g.gasPerDAByte + g.daOverheadGas) + dataGas := big.NewInt(int64(dataLen)*gasPerDAByte + daOverheadGas) dataGasEstimate := new(big.Int).Mul(dataGas, daGasPrice) - dataGasEstimate = new(big.Int).Div(new(big.Int).Mul(dataGasEstimate, big.NewInt(g.daMultiplier)), big.NewInt(daMultiplierBase)) + dataGasEstimate = new(big.Int).Div(new(big.Int).Mul(dataGasEstimate, big.NewInt(daMultiplier)), big.NewInt(daMultiplierBase)) - return ccipcalc.CalculateUsdPerUnitGas(dataGasEstimate, wrappedNativePrice) + return ccipcalc.CalculateUsdPerUnitGas(dataGasEstimate, wrappedNativePrice), nil } diff --git a/core/services/ocr2/plugins/ccip/prices/da_price_estimator_test.go b/core/services/ocr2/plugins/ccip/prices/da_price_estimator_test.go index 2f8616a866..158f02c4f0 100644 --- a/core/services/ocr2/plugins/ccip/prices/da_price_estimator_test.go +++ b/core/services/ocr2/plugins/ccip/prices/da_price_estimator_test.go @@ -2,6 +2,7 @@ package prices import ( "context" + "errors" "math/big" "testing" @@ -11,6 +12,7 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups/mocks" + ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" ) func encodeGasPrice(daPrice, execPrice *big.Int) *big.Int { @@ -325,14 +327,17 @@ func TestDAPriceEstimator_EstimateMsgCostUSD(t *testing.T) { execCostUSD := big.NewInt(100_000) testCases := []struct { - name string - gasPrice *big.Int - wrappedNativePrice *big.Int - msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta - daOverheadGas int64 - gasPerDAByte int64 - daMultiplier int64 - expUSD *big.Int + name string + gasPrice *big.Int + wrappedNativePrice *big.Int + msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta + daOverheadGas int64 + gasPerDAByte int64 + daMultiplier int64 + expUSD *big.Int + onRampConfig cciptypes.OnRampDynamicConfig + execEstimatorResponse []any + execEstimatorErr error }{ { name: "only DA overhead", @@ -345,10 +350,8 @@ func TestDAPriceEstimator_EstimateMsgCostUSD(t *testing.T) { SourceTokenData: [][]byte{}, }, }, - daOverheadGas: 100_000, - gasPerDAByte: 0, - daMultiplier: 10_000, // 1x multiplier - expUSD: new(big.Int).Add(execCostUSD, big.NewInt(100_000e9)), + expUSD: new(big.Int).Add(execCostUSD, big.NewInt(100_000e9)), + execEstimatorResponse: []any{int64(100_000), int64(0), int64(10_000), nil}, }, { name: "include message data gas", @@ -363,10 +366,8 @@ func TestDAPriceEstimator_EstimateMsgCostUSD(t *testing.T) { }, }, }, - daOverheadGas: 100_000, - gasPerDAByte: 16, - daMultiplier: 10_000, // 1x multiplier - expUSD: new(big.Int).Add(execCostUSD, big.NewInt(134_208e9)), + expUSD: new(big.Int).Add(execCostUSD, big.NewInt(134_208e9)), + execEstimatorResponse: []any{int64(100_000), int64(16), int64(10_000), nil}, }, { name: "zero DA price", @@ -379,10 +380,7 @@ func TestDAPriceEstimator_EstimateMsgCostUSD(t *testing.T) { SourceTokenData: [][]byte{}, }, }, - daOverheadGas: 100_000, - gasPerDAByte: 16, - daMultiplier: 10_000, // 1x multiplier - expUSD: execCostUSD, + expUSD: execCostUSD, }, { name: "double native price", @@ -395,10 +393,8 @@ func TestDAPriceEstimator_EstimateMsgCostUSD(t *testing.T) { SourceTokenData: [][]byte{}, }, }, - daOverheadGas: 100_000, - gasPerDAByte: 0, - daMultiplier: 10_000, // 1x multiplier - expUSD: new(big.Int).Add(execCostUSD, big.NewInt(200_000e9)), + expUSD: new(big.Int).Add(execCostUSD, big.NewInt(200_000e9)), + execEstimatorResponse: []any{int64(100_000), int64(0), int64(10_000), nil}, }, { name: "half multiplier", @@ -411,30 +407,66 @@ func TestDAPriceEstimator_EstimateMsgCostUSD(t *testing.T) { SourceTokenData: [][]byte{}, }, }, - daOverheadGas: 100_000, - gasPerDAByte: 0, - daMultiplier: 5_000, // 0.5x multiplier - expUSD: new(big.Int).Add(execCostUSD, big.NewInt(50_000e9)), + expUSD: new(big.Int).Add(execCostUSD, big.NewInt(50_000e9)), + execEstimatorResponse: []any{int64(100_000), int64(0), int64(5_000), nil}, + }, + { + name: "onRamp reader error", + gasPrice: encodeGasPrice(big.NewInt(1e9), big.NewInt(0)), // 1 gwei DA price, 0 exec price + wrappedNativePrice: big.NewInt(1e18), // $1 + msg: cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta{ + EVM2EVMMessage: cciptypes.EVM2EVMMessage{ + Data: []byte{}, + TokenAmounts: []cciptypes.TokenAmount{}, + SourceTokenData: [][]byte{}, + }, + }, + execEstimatorResponse: []any{int64(0), int64(0), int64(0), errors.New("some reader error")}, + }, + { + name: "execEstimator error", + gasPrice: encodeGasPrice(big.NewInt(1e9), big.NewInt(0)), // 1 gwei DA price, 0 exec price + wrappedNativePrice: big.NewInt(1e18), // $1 + msg: cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta{ + EVM2EVMMessage: cciptypes.EVM2EVMMessage{ + Data: []byte{}, + TokenAmounts: []cciptypes.TokenAmount{}, + SourceTokenData: [][]byte{}, + }, + }, + execEstimatorErr: errors.New("some estimator error"), }, } for _, tc := range testCases { - execEstimator := NewMockGasPriceEstimator(t) - execEstimator.On("EstimateMsgCostUSD", mock.Anything, tc.wrappedNativePrice, tc.msg).Return(execCostUSD, nil) - t.Run(tc.name, func(t *testing.T) { + execEstimator := NewMockGasPriceEstimator(t) + execEstimator.On("EstimateMsgCostUSD", mock.Anything, tc.wrappedNativePrice, tc.msg). + Return(execCostUSD, tc.execEstimatorErr) + + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + if len(tc.execEstimatorResponse) > 0 { + feeEstimatorConfig.On("GetDataAvailabilityConfig", mock.Anything). + Return(tc.execEstimatorResponse...) + } + g := DAGasPriceEstimator{ execEstimator: execEstimator, l1Oracle: nil, priceEncodingLength: daGasPriceEncodingLength, - daOverheadGas: tc.daOverheadGas, - gasPerDAByte: tc.gasPerDAByte, - daMultiplier: tc.daMultiplier, + feeEstimatorConfig: feeEstimatorConfig, } costUSD, err := g.EstimateMsgCostUSD(tc.gasPrice, tc.wrappedNativePrice, tc.msg) - assert.NoError(t, err) - assert.Equal(t, tc.expUSD, costUSD) + + switch { + case len(tc.execEstimatorResponse) == 4 && tc.execEstimatorResponse[3] != nil, + tc.execEstimatorErr != nil: + assert.Error(t, err) + default: + assert.NoError(t, err) + assert.Equal(t, tc.expUSD, costUSD) + } }) } } diff --git a/core/services/ocr2/plugins/ccip/prices/gas_price_estimator.go b/core/services/ocr2/plugins/ccip/prices/gas_price_estimator.go index 49a6fbcc4a..4aac664e33 100644 --- a/core/services/ocr2/plugins/ccip/prices/gas_price_estimator.go +++ b/core/services/ocr2/plugins/ccip/prices/gas_price_estimator.go @@ -3,6 +3,8 @@ package prices import ( "math/big" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" + "github.com/Masterminds/semver/v3" "github.com/pkg/errors" @@ -47,12 +49,13 @@ func NewGasPriceEstimatorForCommitPlugin( maxExecGasPrice *big.Int, daDeviationPPB int64, execDeviationPPB int64, + feeEstimatorConfig ccipdata.FeeEstimatorConfigReader, ) (GasPriceEstimatorCommit, error) { switch commitStoreVersion.String() { case "1.0.0", "1.1.0": return NewExecGasPriceEstimator(estimator, maxExecGasPrice, execDeviationPPB), nil case "1.2.0": - return NewDAGasPriceEstimator(estimator, maxExecGasPrice, execDeviationPPB, daDeviationPPB), nil + return NewDAGasPriceEstimator(estimator, maxExecGasPrice, execDeviationPPB, daDeviationPPB, feeEstimatorConfig), nil default: return nil, errors.Errorf("Invalid commitStore version: %s", commitStoreVersion) } diff --git a/core/services/relay/evm/ccip.go b/core/services/relay/evm/ccip.go index 34a732e145..945763de85 100644 --- a/core/services/relay/evm/ccip.go +++ b/core/services/relay/evm/ccip.go @@ -6,18 +6,16 @@ import ( "math/big" "time" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" - + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/logger" - - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices" - - cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" ) var _ cciptypes.CommitStoreReader = (*IncompleteSourceCommitStoreReader)(nil) @@ -26,16 +24,18 @@ var _ cciptypes.CommitStoreReader = (*IncompleteDestCommitStoreReader)(nil) // IncompleteSourceCommitStoreReader is an implementation of CommitStoreReader with the only valid methods being // GasPriceEstimator, ChangeConfig, and OffchainConfig type IncompleteSourceCommitStoreReader struct { - estimator gas.EvmFeeEstimator - gasPriceEstimator *prices.DAGasPriceEstimator - sourceMaxGasPrice *big.Int - offchainConfig cciptypes.CommitOffchainConfig + estimator gas.EvmFeeEstimator + gasPriceEstimator *prices.DAGasPriceEstimator + sourceMaxGasPrice *big.Int + offchainConfig cciptypes.CommitOffchainConfig + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider } -func NewIncompleteSourceCommitStoreReader(estimator gas.EvmFeeEstimator, sourceMaxGasPrice *big.Int) *IncompleteSourceCommitStoreReader { +func NewIncompleteSourceCommitStoreReader(estimator gas.EvmFeeEstimator, sourceMaxGasPrice *big.Int, feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider) *IncompleteSourceCommitStoreReader { return &IncompleteSourceCommitStoreReader{ - estimator: estimator, - sourceMaxGasPrice: sourceMaxGasPrice, + estimator: estimator, + sourceMaxGasPrice: sourceMaxGasPrice, + feeEstimatorConfig: feeEstimatorConfig, } } @@ -55,6 +55,7 @@ func (i *IncompleteSourceCommitStoreReader) ChangeConfig(ctx context.Context, on i.sourceMaxGasPrice, int64(offchainConfigParsed.ExecGasPriceDeviationPPB), int64(offchainConfigParsed.DAGasPriceDeviationPPB), + i.feeEstimatorConfig, ) i.offchainConfig = ccip.NewCommitOffchainConfig( offchainConfigParsed.ExecGasPriceDeviationPPB, @@ -133,8 +134,15 @@ type IncompleteDestCommitStoreReader struct { cs cciptypes.CommitStoreReader } -func NewIncompleteDestCommitStoreReader(lggr logger.Logger, versionFinder ccip.VersionFinder, address cciptypes.Address, ec client.Client, lp logpoller.LogPoller) (*IncompleteDestCommitStoreReader, error) { - cs, err := ccip.NewCommitStoreReader(lggr, versionFinder, address, ec, lp) +func NewIncompleteDestCommitStoreReader( + lggr logger.Logger, + versionFinder ccip.VersionFinder, + address cciptypes.Address, + ec client.Client, + lp logpoller.LogPoller, + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider, +) (*IncompleteDestCommitStoreReader, error) { + cs, err := ccip.NewCommitStoreReader(lggr, versionFinder, address, ec, lp, feeEstimatorConfig) if err != nil { return nil, err } diff --git a/core/services/relay/evm/commit_provider.go b/core/services/relay/evm/commit_provider.go index 35de8efbd8..780d1cee7b 100644 --- a/core/services/relay/evm/commit_provider.go +++ b/core/services/relay/evm/commit_provider.go @@ -18,18 +18,20 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig" ) var _ commontypes.CCIPCommitProvider = (*SrcCommitProvider)(nil) var _ commontypes.CCIPCommitProvider = (*DstCommitProvider)(nil) type SrcCommitProvider struct { - lggr logger.Logger - startBlock uint64 - client client.Client - lp logpoller.LogPoller - estimator gas.EvmFeeEstimator - maxGasPrice *big.Int + lggr logger.Logger + startBlock uint64 + client client.Client + lp logpoller.LogPoller + estimator gas.EvmFeeEstimator + maxGasPrice *big.Int + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider // these values will be lazily initialized seenOnRampAddress *cciptypes.Address @@ -44,14 +46,16 @@ func NewSrcCommitProvider( lp logpoller.LogPoller, srcEstimator gas.EvmFeeEstimator, maxGasPrice *big.Int, + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider, ) commontypes.CCIPCommitProvider { return &SrcCommitProvider{ - lggr: lggr, - startBlock: startBlock, - client: client, - lp: lp, - estimator: srcEstimator, - maxGasPrice: maxGasPrice, + lggr: lggr, + startBlock: startBlock, + client: client, + lp: lp, + estimator: srcEstimator, + maxGasPrice: maxGasPrice, + feeEstimatorConfig: feeEstimatorConfig, } } @@ -65,6 +69,7 @@ type DstCommitProvider struct { configWatcher *configWatcher gasEstimator gas.EvmFeeEstimator maxGasPrice big.Int + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider // these values will be lazily initialized seenCommitStoreAddress *cciptypes.Address @@ -81,6 +86,7 @@ func NewDstCommitProvider( maxGasPrice big.Int, contractTransmitter contractTransmitter, configWatcher *configWatcher, + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider, ) commontypes.CCIPCommitProvider { return &DstCommitProvider{ lggr: lggr, @@ -92,6 +98,7 @@ func NewDstCommitProvider( configWatcher: configWatcher, gasEstimator: gasEstimator, maxGasPrice: maxGasPrice, + feeEstimatorConfig: feeEstimatorConfig, } } @@ -170,13 +177,13 @@ func (P *DstCommitProvider) Close() error { if P.seenCommitStoreAddress == nil { return nil } - return ccip.CloseCommitStoreReader(P.lggr, versionFinder, *P.seenCommitStoreAddress, P.client, P.lp) + return ccip.CloseCommitStoreReader(P.lggr, versionFinder, *P.seenCommitStoreAddress, P.client, P.lp, P.feeEstimatorConfig) }) unregisterFuncs = append(unregisterFuncs, func() error { if P.seenOffRampAddress == nil { return nil } - return ccip.CloseOffRampReader(P.lggr, versionFinder, *P.seenOffRampAddress, P.client, P.lp, nil, big.NewInt(0)) + return ccip.CloseOffRampReader(P.lggr, versionFinder, *P.seenOffRampAddress, P.client, P.lp, nil, big.NewInt(0), P.feeEstimatorConfig) }) var multiErr error @@ -241,7 +248,7 @@ func (P *DstCommitProvider) NewPriceGetter(ctx context.Context) (priceGetter cci } func (P *SrcCommitProvider) NewCommitStoreReader(ctx context.Context, commitStoreAddress cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { - commitStoreReader = NewIncompleteSourceCommitStoreReader(P.estimator, P.maxGasPrice) + commitStoreReader = NewIncompleteSourceCommitStoreReader(P.estimator, P.maxGasPrice, P.feeEstimatorConfig) return } @@ -249,7 +256,7 @@ func (P *DstCommitProvider) NewCommitStoreReader(ctx context.Context, commitStor P.seenCommitStoreAddress = &commitStoreAddress versionFinder := ccip.NewEvmVersionFinder() - commitStoreReader, err = NewIncompleteDestCommitStoreReader(P.lggr, versionFinder, commitStoreAddress, P.client, P.lp) + commitStoreReader, err = NewIncompleteDestCommitStoreReader(P.lggr, versionFinder, commitStoreAddress, P.client, P.lp, P.feeEstimatorConfig) return } @@ -259,7 +266,12 @@ func (P *SrcCommitProvider) NewOnRampReader(ctx context.Context, onRampAddress c P.seenDestChainSelector = &destChainSelector versionFinder := ccip.NewEvmVersionFinder() + onRampReader, err = ccip.NewOnRampReader(P.lggr, versionFinder, sourceChainSelector, destChainSelector, onRampAddress, P.lp, P.client) + if err != nil { + return nil, err + } + P.feeEstimatorConfig.SetOnRampReader(onRampReader) return } @@ -272,7 +284,7 @@ func (P *SrcCommitProvider) NewOffRampReader(ctx context.Context, offRampAddr cc } func (P *DstCommitProvider) NewOffRampReader(ctx context.Context, offRampAddr cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { - offRampReader, err = ccip.NewOffRampReader(P.lggr, P.versionFinder, offRampAddr, P.client, P.lp, P.gasEstimator, &P.maxGasPrice, true) + offRampReader, err = ccip.NewOffRampReader(P.lggr, P.versionFinder, offRampAddr, P.client, P.lp, P.gasEstimator, &P.maxGasPrice, true, P.feeEstimatorConfig) return } diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index fe84949903..85cb3486ca 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -45,6 +45,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/llo" "github.com/smartcontractkit/chainlink/v2/core/services/llo/bm" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig" lloconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/llo/config" mercuryconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/mercury/config" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" @@ -406,6 +407,8 @@ func (r *Relayer) NewCCIPCommitProvider(rargs commontypes.RelayArgs, pargs commo sourceStartBlock := commitPluginConfig.SourceStartBlock destStartBlock := commitPluginConfig.DestStartBlock + feeEstimatorConfig := estimatorconfig.NewFeeEstimatorConfigService() + // The src chain implementation of this provider does not need a configWatcher or contractTransmitter; // bail early. if commitPluginConfig.IsSourceProvider { @@ -416,6 +419,7 @@ func (r *Relayer) NewCCIPCommitProvider(rargs commontypes.RelayArgs, pargs commo r.chain.LogPoller(), r.chain.GasEstimator(), r.chain.Config().EVM().GasEstimator().PriceMax().ToInt(), + feeEstimatorConfig, ), nil } @@ -451,6 +455,7 @@ func (r *Relayer) NewCCIPCommitProvider(rargs commontypes.RelayArgs, pargs commo *r.chain.Config().EVM().GasEstimator().PriceMax().ToInt(), *contractTransmitter, configWatcher, + feeEstimatorConfig, ), nil } @@ -473,6 +478,8 @@ func (r *Relayer) NewCCIPExecProvider(rargs commontypes.RelayArgs, pargs commont usdcConfig := execPluginConfig.USDCConfig + feeEstimatorConfig := estimatorconfig.NewFeeEstimatorConfigService() + // The src chain implementation of this provider does not need a configWatcher or contractTransmitter; // bail early. if execPluginConfig.IsSourceProvider { @@ -489,6 +496,7 @@ func (r *Relayer) NewCCIPExecProvider(rargs commontypes.RelayArgs, pargs commont int(usdcConfig.AttestationAPITimeoutSeconds), usdcConfig.AttestationAPIIntervalMilliseconds, usdcConfig.SourceMessageTransmitterAddress, + feeEstimatorConfig, ) } @@ -524,6 +532,7 @@ func (r *Relayer) NewCCIPExecProvider(rargs commontypes.RelayArgs, pargs commont configWatcher, r.chain.GasEstimator(), *r.chain.Config().EVM().GasEstimator().PriceMax().ToInt(), + feeEstimatorConfig, r.chain.TxManager(), cciptypes.Address(rargs.ContractID), ) diff --git a/core/services/relay/evm/exec_provider.go b/core/services/relay/evm/exec_provider.go index d0e567af73..e50ae41351 100644 --- a/core/services/relay/evm/exec_provider.go +++ b/core/services/relay/evm/exec_provider.go @@ -23,6 +23,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/tokendata/usdc" ) @@ -40,6 +41,8 @@ type SrcExecProvider struct { usdcAttestationAPIIntervalMilliseconds int usdcSrcMsgTransmitterAddr common.Address + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider + // these values are nil and are updated for Close() seenOnRampAddress *cciptypes.Address seenSourceChainSelector *uint64 @@ -59,6 +62,7 @@ func NewSrcExecProvider( usdcAttestationAPITimeoutSeconds int, usdcAttestationAPIIntervalMilliseconds int, usdcSrcMsgTransmitterAddr common.Address, + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider, ) (commontypes.CCIPExecProvider, error) { var usdcReader *ccip.USDCReaderImpl var err error @@ -82,6 +86,7 @@ func NewSrcExecProvider( usdcAttestationAPITimeoutSeconds: usdcAttestationAPITimeoutSeconds, usdcAttestationAPIIntervalMilliseconds: usdcAttestationAPIIntervalMilliseconds, usdcSrcMsgTransmitterAddr: usdcSrcMsgTransmitterAddr, + feeEstimatorConfig: feeEstimatorConfig, }, nil } @@ -163,7 +168,7 @@ func (s *SrcExecProvider) GetTransactionStatus(ctx context.Context, transactionI } func (s *SrcExecProvider) NewCommitStoreReader(ctx context.Context, addr cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { - commitStoreReader = NewIncompleteSourceCommitStoreReader(s.estimator, s.maxGasPrice) + commitStoreReader = NewIncompleteSourceCommitStoreReader(s.estimator, s.maxGasPrice, s.feeEstimatorConfig) return } @@ -176,6 +181,10 @@ func (s *SrcExecProvider) NewOnRampReader(ctx context.Context, onRampAddress cci versionFinder := ccip.NewEvmVersionFinder() onRampReader, err = ccip.NewOnRampReader(s.lggr, versionFinder, sourceChainSelector, destChainSelector, onRampAddress, s.lp, s.client) + if err != nil { + return nil, err + } + s.feeEstimatorConfig.SetOnRampReader(onRampReader) return } @@ -236,6 +245,7 @@ type DstExecProvider struct { configWatcher *configWatcher gasEstimator gas.EvmFeeEstimator maxGasPrice big.Int + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider txm txmgr.TxManager offRampAddress cciptypes.Address @@ -253,6 +263,7 @@ func NewDstExecProvider( configWatcher *configWatcher, gasEstimator gas.EvmFeeEstimator, maxGasPrice big.Int, + feeEstimatorConfig estimatorconfig.FeeEstimatorConfigProvider, txm txmgr.TxManager, offRampAddress cciptypes.Address, ) (commontypes.CCIPExecProvider, error) { @@ -266,6 +277,7 @@ func NewDstExecProvider( configWatcher: configWatcher, gasEstimator: gasEstimator, maxGasPrice: maxGasPrice, + feeEstimatorConfig: feeEstimatorConfig, txm: txm, offRampAddress: offRampAddress, }, nil @@ -295,10 +307,10 @@ func (d *DstExecProvider) Close() error { if d.seenCommitStoreAddr == nil { return nil } - return ccip.CloseCommitStoreReader(d.lggr, versionFinder, *d.seenCommitStoreAddr, d.client, d.lp) + return ccip.CloseCommitStoreReader(d.lggr, versionFinder, *d.seenCommitStoreAddr, d.client, d.lp, d.feeEstimatorConfig) }) unregisterFuncs = append(unregisterFuncs, func() error { - return ccip.CloseOffRampReader(d.lggr, versionFinder, d.offRampAddress, d.client, d.lp, nil, big.NewInt(0)) + return ccip.CloseOffRampReader(d.lggr, versionFinder, d.offRampAddress, d.client, d.lp, nil, big.NewInt(0), d.feeEstimatorConfig) }) var multiErr error @@ -346,12 +358,12 @@ func (d *DstExecProvider) NewCommitStoreReader(ctx context.Context, addr cciptyp d.seenCommitStoreAddr = &addr versionFinder := ccip.NewEvmVersionFinder() - commitStoreReader, err = NewIncompleteDestCommitStoreReader(d.lggr, versionFinder, addr, d.client, d.lp) + commitStoreReader, err = NewIncompleteDestCommitStoreReader(d.lggr, versionFinder, addr, d.client, d.lp, d.feeEstimatorConfig) return } func (d *DstExecProvider) NewOffRampReader(ctx context.Context, offRampAddress cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { - offRampReader, err = ccip.NewOffRampReader(d.lggr, d.versionFinder, offRampAddress, d.client, d.lp, d.gasEstimator, &d.maxGasPrice, true) + offRampReader, err = ccip.NewOffRampReader(d.lggr, d.versionFinder, offRampAddress, d.client, d.lp, d.gasEstimator, &d.maxGasPrice, true, d.feeEstimatorConfig) return } From b84dc2c21437369cc7dfa943625339abc3324e0a Mon Sep 17 00:00:00 2001 From: Valerii Kabisov <172247313+valerii-kabisov-cll@users.noreply.github.com> Date: Wed, 25 Sep 2024 00:31:37 +0900 Subject: [PATCH 19/31] =?UTF-8?q?[CCIP-3376]=20Add=20component=20into=20CC?= =?UTF-8?q?IP=20price=20estimators=20to=20account=20for=20c=E2=80=A6=20(#1?= =?UTF-8?q?446)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [https://smartcontract-it.atlassian.net/browse/CCIP-3376](https://smartcontract-it.atlassian.net/browse/CCIP-3376) The CCIP gas limit is set by customers. This cannot be changed because this limit is directly used within the contracts (which expect limits in terms of ETH). Also, the L2 gas price returned from our current estimators for Mantle is in MNT. Therefore, the final formula needed for CCIP to calculate L2 transaction fees is the following: gasLimit_EVM * (gasPrice_Mantle * TokenRatio) Therefore, we need to multiply the gas price returned from our gas estimators by tokenRatio for CCIP. This should happen in the CCIP price estimator. Currently, we have no component here for chain-specific logic, so we will need to add this functionality. --------- Co-authored-by: Matt Yang --- .mockery.yaml | 7 +- .../plugins/ccip/estimatorconfig/config.go | 37 +++++- .../ccip/estimatorconfig/config_test.go | 64 +++++++++++ .../interceptors/mantle/interceptor.go | 80 +++++++++++++ .../interceptors/mantle/interceptor_test.go | 96 ++++++++++++++++ .../mocks/gas_price_interceptor_mock.go | 106 ++++++++++++++++++ .../ccipdata/commit_store_reader_test.go | 11 +- .../internal/ccipdata/fee_estimator_config.go | 2 + .../mocks/fee_estimator_config_mock.go | 71 ++++++++++++ .../plugins/ccip/prices/da_price_estimator.go | 10 +- .../ccip/prices/da_price_estimator_test.go | 52 ++++++++- core/services/relay/evm/evm.go | 22 ++++ .../evm/interceptors/mantle/interceptor.go | 80 +++++++++++++ .../interceptors/mantle/interceptor_test.go | 96 ++++++++++++++++ 14 files changed, 725 insertions(+), 9 deletions(-) create mode 100644 core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go create mode 100644 core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go create mode 100644 core/services/ocr2/plugins/ccip/estimatorconfig/mocks/gas_price_interceptor_mock.go create mode 100644 core/services/relay/evm/interceptors/mantle/interceptor.go create mode 100644 core/services/relay/evm/interceptors/mantle/interceptor_test.go diff --git a/.mockery.yaml b/.mockery.yaml index 3911f3b6f5..471f931856 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -453,7 +453,7 @@ packages: filename: optimism_portal2_interface.go outpkg: mock_optimism_portal_2 interfaces: - OptimismPortal2Interface: + OptimismPortal2Interface: github.com/smartcontractkit/chainlink/v2/core/gethwrappers/liquiditymanager/generated/optimism_dispute_game_factory: config: dir: core/gethwrappers/liquiditymanager/mocks/mock_optimism_dispute_game_factory/ @@ -493,6 +493,11 @@ packages: USDCReader: config: filename: usdc_reader_mock.go + github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig: + interfaces: + GasPriceInterceptor: + config: + filename: gas_price_interceptor_mock.go github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/batchreader: config: filename: token_pool_batched_reader_mock.go diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/config.go b/core/services/ocr2/plugins/ccip/estimatorconfig/config.go index 2eda88d441..0e66f4fb39 100644 --- a/core/services/ocr2/plugins/ccip/estimatorconfig/config.go +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/config.go @@ -3,6 +3,7 @@ package estimatorconfig import ( "context" "errors" + "math/big" "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" ) @@ -13,11 +14,18 @@ import ( // fields for the daGasEstimator from the encapsulated onRampReader. type FeeEstimatorConfigProvider interface { SetOnRampReader(reader ccip.OnRampReader) + AddGasPriceInterceptor(GasPriceInterceptor) + ModifyGasPriceComponents(ctx context.Context, execGasPrice, daGasPrice *big.Int) (modExecGasPrice, modDAGasPrice *big.Int, err error) GetDataAvailabilityConfig(ctx context.Context) (destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps int64, err error) } +type GasPriceInterceptor interface { + ModifyGasPriceComponents(ctx context.Context, execGasPrice, daGasPrice *big.Int) (modExecGasPrice, modDAGasPrice *big.Int, err error) +} + type FeeEstimatorConfigService struct { - onRampReader ccip.OnRampReader + onRampReader ccip.OnRampReader + gasPriceInterceptors []GasPriceInterceptor } func NewFeeEstimatorConfigService() *FeeEstimatorConfigService { @@ -47,3 +55,30 @@ func (c *FeeEstimatorConfigService) GetDataAvailabilityConfig(ctx context.Contex int64(cfg.DestDataAvailabilityMultiplierBps), err } + +// AddGasPriceInterceptor adds price interceptors that can modify gas price. +func (c *FeeEstimatorConfigService) AddGasPriceInterceptor(gpi GasPriceInterceptor) { + if gpi != nil { + c.gasPriceInterceptors = append(c.gasPriceInterceptors, gpi) + } +} + +// ModifyGasPriceComponents applies gasPrice interceptors and returns modified gasPrice. +func (c *FeeEstimatorConfigService) ModifyGasPriceComponents(ctx context.Context, gasPrice, daGasPrice *big.Int) (*big.Int, *big.Int, error) { + if len(c.gasPriceInterceptors) == 0 { + return gasPrice, daGasPrice, nil + } + + // values are mutable, it is necessary to copy the values to protect the arguments from modification. + cpGasPrice := new(big.Int).Set(gasPrice) + cpDAGasPrice := new(big.Int).Set(daGasPrice) + + var err error + for _, interceptor := range c.gasPriceInterceptors { + if cpGasPrice, cpDAGasPrice, err = interceptor.ModifyGasPriceComponents(ctx, cpGasPrice, cpDAGasPrice); err != nil { + return nil, nil, err + } + } + + return cpGasPrice, cpDAGasPrice, nil +} diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go b/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go index 05226f8b48..0ed7510591 100644 --- a/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go @@ -3,10 +3,13 @@ package estimatorconfig_test import ( "context" "errors" + "math/big" "testing" "github.com/stretchr/testify/require" + mocks2 "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig/mocks" + "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" @@ -43,3 +46,64 @@ func TestFeeEstimatorConfigService(t *testing.T) { _, _, _, err = svc.GetDataAvailabilityConfig(ctx) require.Error(t, err) } + +func TestModifyGasPriceComponents(t *testing.T) { + t.Run("success modification", func(t *testing.T) { + svc := estimatorconfig.NewFeeEstimatorConfigService() + ctx := context.Background() + + initialExecGasPrice, initialDaGasPrice := big.NewInt(10), big.NewInt(1) + + gpi1 := mocks2.NewGasPriceInterceptor(t) + svc.AddGasPriceInterceptor(gpi1) + + // change in first interceptor + firstModExecGasPrice, firstModDaGasPrice := big.NewInt(5), big.NewInt(2) + gpi1.On("ModifyGasPriceComponents", ctx, initialExecGasPrice, initialDaGasPrice). + Return(firstModExecGasPrice, firstModDaGasPrice, nil) + + gpi2 := mocks2.NewGasPriceInterceptor(t) + svc.AddGasPriceInterceptor(gpi2) + + // change in second iterceptor + secondModExecGasPrice, secondModDaGasPrice := big.NewInt(50), big.NewInt(20) + gpi2.On("ModifyGasPriceComponents", ctx, firstModExecGasPrice, firstModDaGasPrice). + Return(secondModExecGasPrice, secondModDaGasPrice, nil) + + // has to return second interceptor values + resGasPrice, resDAGasPrice, err := svc.ModifyGasPriceComponents(ctx, initialExecGasPrice, initialDaGasPrice) + require.NoError(t, err) + require.Equal(t, secondModExecGasPrice.Int64(), resGasPrice.Int64()) + require.Equal(t, secondModDaGasPrice.Int64(), resDAGasPrice.Int64()) + }) + + t.Run("error modification", func(t *testing.T) { + svc := estimatorconfig.NewFeeEstimatorConfigService() + ctx := context.Background() + + initialExecGasPrice, initialDaGasPrice := big.NewInt(10), big.NewInt(1) + gpi1 := mocks2.NewGasPriceInterceptor(t) + svc.AddGasPriceInterceptor(gpi1) + gpi1.On("ModifyGasPriceComponents", ctx, initialExecGasPrice, initialDaGasPrice). + Return(nil, nil, errors.New("test")) + + // has to return second interceptor values + _, _, err := svc.ModifyGasPriceComponents(ctx, initialExecGasPrice, initialDaGasPrice) + require.Error(t, err) + }) + + t.Run("without interceptors", func(t *testing.T) { + svc := estimatorconfig.NewFeeEstimatorConfigService() + ctx := context.Background() + + initialExecGasPrice, initialDaGasPrice := big.NewInt(10), big.NewInt(1) + + // has to return second interceptor values + resGasPrice, resDAGasPrice, err := svc.ModifyGasPriceComponents(ctx, initialExecGasPrice, initialDaGasPrice) + require.NoError(t, err) + + // values should not be modified + require.Equal(t, initialExecGasPrice, resGasPrice) + require.Equal(t, initialDaGasPrice, resDAGasPrice) + }) +} diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go b/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go new file mode 100644 index 0000000000..8d5d3bb6c2 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor.go @@ -0,0 +1,80 @@ +package mantle + +import ( + "context" + "fmt" + "math/big" + "strings" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + evmClient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" +) + +const ( + // tokenRatio is not volatile and can be requested not often. + tokenRatioUpdateInterval = 60 * time.Minute + // tokenRatio fetches the tokenRatio used for Mantle's gas price calculation + tokenRatioMethod = "tokenRatio" + mantleTokenRatioAbiString = `[{"inputs":[],"name":"tokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` +) + +type Interceptor struct { + client evmClient.Client + tokenRatioCallData []byte + tokenRatio *big.Int + tokenRatioLastUpdate time.Time +} + +func NewInterceptor(_ context.Context, client evmClient.Client) (*Interceptor, error) { + // Encode calldata for tokenRatio method + tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(mantleTokenRatioAbiString)) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for Mantle; %v", tokenRatioMethod, err) + } + tokenRatioCallData, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for Mantle; %v", tokenRatioMethod, err) + } + + return &Interceptor{ + client: client, + tokenRatioCallData: tokenRatioCallData, + }, nil +} + +// ModifyGasPriceComponents returns modified gasPrice. +func (i *Interceptor) ModifyGasPriceComponents(ctx context.Context, execGasPrice, daGasPrice *big.Int) (*big.Int, *big.Int, error) { + if time.Since(i.tokenRatioLastUpdate) > tokenRatioUpdateInterval { + mantleTokenRatio, err := i.getMantleTokenRatio(ctx) + if err != nil { + return nil, nil, err + } + + i.tokenRatio, i.tokenRatioLastUpdate = mantleTokenRatio, time.Now() + } + + // multiply daGasPrice and execGas price by tokenRatio + newExecGasPrice := new(big.Int).Mul(execGasPrice, i.tokenRatio) + newDAGasPrice := new(big.Int).Mul(daGasPrice, i.tokenRatio) + return newExecGasPrice, newDAGasPrice, nil +} + +// getMantleTokenRatio Requests and returns a token ratio value for the Mantle chain. +func (i *Interceptor) getMantleTokenRatio(ctx context.Context) (*big.Int, error) { + precompile := common.HexToAddress(rollups.OPGasOracleAddress) + tokenRatio, err := i.client.CallContract(ctx, ethereum.CallMsg{ + To: &precompile, + Data: i.tokenRatioCallData, + }, nil) + + if err != nil { + return nil, fmt.Errorf("getMantleTokenRatio call failed: %w", err) + } + + return new(big.Int).SetBytes(tokenRatio), nil +} diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go b/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go new file mode 100644 index 0000000000..9134d996c2 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle/interceptor_test.go @@ -0,0 +1,96 @@ +package mantle + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" +) + +func TestInterceptor(t *testing.T) { + ethClient := mocks.NewClient(t) + ctx := context.Background() + + tokenRatio := big.NewInt(10) + interceptor, err := NewInterceptor(ctx, ethClient) + require.NoError(t, err) + + // request token ratio + ethClient.On("CallContract", ctx, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})). + Return(common.BigToHash(tokenRatio).Bytes(), nil).Once() + + modExecGasPrice, modDAGasPrice, err := interceptor.ModifyGasPriceComponents(ctx, big.NewInt(1), big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, int64(10), modExecGasPrice.Int64()) + require.Equal(t, int64(10), modDAGasPrice.Int64()) + + // second call won't invoke eth client + modExecGasPrice, modDAGasPrice, err = interceptor.ModifyGasPriceComponents(ctx, big.NewInt(2), big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, int64(20), modExecGasPrice.Int64()) + require.Equal(t, int64(10), modDAGasPrice.Int64()) +} + +func TestModifyGasPriceComponents(t *testing.T) { + testCases := map[string]struct { + execGasPrice *big.Int + daGasPrice *big.Int + tokenRatio *big.Int + resultExecGasPrice *big.Int + resultDAGasPrice *big.Int + }{ + "regular": { + execGasPrice: big.NewInt(1000), + daGasPrice: big.NewInt(100), + resultExecGasPrice: big.NewInt(2000), + resultDAGasPrice: big.NewInt(200), + tokenRatio: big.NewInt(2), + }, + "zero DAGasPrice": { + execGasPrice: big.NewInt(1000), + daGasPrice: big.NewInt(0), + resultExecGasPrice: big.NewInt(5000), + resultDAGasPrice: big.NewInt(0), + tokenRatio: big.NewInt(5), + }, + "zero ExecGasPrice": { + execGasPrice: big.NewInt(0), + daGasPrice: big.NewInt(10), + resultExecGasPrice: big.NewInt(0), + resultDAGasPrice: big.NewInt(50), + tokenRatio: big.NewInt(5), + }, + "zero token ratio": { + execGasPrice: big.NewInt(15), + daGasPrice: big.NewInt(10), + resultExecGasPrice: big.NewInt(0), + resultDAGasPrice: big.NewInt(0), + tokenRatio: big.NewInt(0), + }, + } + + for tcName, tc := range testCases { + t.Run(tcName, func(t *testing.T) { + ethClient := mocks.NewClient(t) + ctx := context.Background() + + interceptor, err := NewInterceptor(ctx, ethClient) + require.NoError(t, err) + + // request token ratio + ethClient.On("CallContract", ctx, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})). + Return(common.BigToHash(tc.tokenRatio).Bytes(), nil).Once() + + modExecGasPrice, modDAGasPrice, err := interceptor.ModifyGasPriceComponents(ctx, tc.execGasPrice, tc.daGasPrice) + require.NoError(t, err) + require.Equal(t, tc.resultExecGasPrice.Int64(), modExecGasPrice.Int64()) + require.Equal(t, tc.resultDAGasPrice.Int64(), modDAGasPrice.Int64()) + }) + } +} diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/mocks/gas_price_interceptor_mock.go b/core/services/ocr2/plugins/ccip/estimatorconfig/mocks/gas_price_interceptor_mock.go new file mode 100644 index 0000000000..62ec5a2207 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/mocks/gas_price_interceptor_mock.go @@ -0,0 +1,106 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mocks + +import ( + context "context" + big "math/big" + + mock "github.com/stretchr/testify/mock" +) + +// GasPriceInterceptor is an autogenerated mock type for the GasPriceInterceptor type +type GasPriceInterceptor struct { + mock.Mock +} + +type GasPriceInterceptor_Expecter struct { + mock *mock.Mock +} + +func (_m *GasPriceInterceptor) EXPECT() *GasPriceInterceptor_Expecter { + return &GasPriceInterceptor_Expecter{mock: &_m.Mock} +} + +// ModifyGasPriceComponents provides a mock function with given fields: ctx, execGasPrice, daGasPrice +func (_m *GasPriceInterceptor) ModifyGasPriceComponents(ctx context.Context, execGasPrice *big.Int, daGasPrice *big.Int) (*big.Int, *big.Int, error) { + ret := _m.Called(ctx, execGasPrice, daGasPrice) + + if len(ret) == 0 { + panic("no return value specified for ModifyGasPriceComponents") + } + + var r0 *big.Int + var r1 *big.Int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int, *big.Int) (*big.Int, *big.Int, error)); ok { + return rf(ctx, execGasPrice, daGasPrice) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int, *big.Int) *big.Int); ok { + r0 = rf(ctx, execGasPrice, daGasPrice) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int, *big.Int) *big.Int); ok { + r1 = rf(ctx, execGasPrice, daGasPrice) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*big.Int) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, *big.Int, *big.Int) error); ok { + r2 = rf(ctx, execGasPrice, daGasPrice) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GasPriceInterceptor_ModifyGasPriceComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ModifyGasPriceComponents' +type GasPriceInterceptor_ModifyGasPriceComponents_Call struct { + *mock.Call +} + +// ModifyGasPriceComponents is a helper method to define mock.On call +// - ctx context.Context +// - execGasPrice *big.Int +// - daGasPrice *big.Int +func (_e *GasPriceInterceptor_Expecter) ModifyGasPriceComponents(ctx interface{}, execGasPrice interface{}, daGasPrice interface{}) *GasPriceInterceptor_ModifyGasPriceComponents_Call { + return &GasPriceInterceptor_ModifyGasPriceComponents_Call{Call: _e.mock.On("ModifyGasPriceComponents", ctx, execGasPrice, daGasPrice)} +} + +func (_c *GasPriceInterceptor_ModifyGasPriceComponents_Call) Run(run func(ctx context.Context, execGasPrice *big.Int, daGasPrice *big.Int)) *GasPriceInterceptor_ModifyGasPriceComponents_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int), args[2].(*big.Int)) + }) + return _c +} + +func (_c *GasPriceInterceptor_ModifyGasPriceComponents_Call) Return(modExecGasPrice *big.Int, modDAGasPrice *big.Int, err error) *GasPriceInterceptor_ModifyGasPriceComponents_Call { + _c.Call.Return(modExecGasPrice, modDAGasPrice, err) + return _c +} + +func (_c *GasPriceInterceptor_ModifyGasPriceComponents_Call) RunAndReturn(run func(context.Context, *big.Int, *big.Int) (*big.Int, *big.Int, error)) *GasPriceInterceptor_ModifyGasPriceComponents_Call { + _c.Call.Return(run) + return _c +} + +// NewGasPriceInterceptor creates a new instance of GasPriceInterceptor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewGasPriceInterceptor(t interface { + mock.TestingT + Cleanup(func()) +}) *GasPriceInterceptor { + mock := &GasPriceInterceptor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go index e9266c4fb2..81807e66fe 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/commit_store_reader_test.go @@ -194,7 +194,14 @@ func TestCommitStoreReaders(t *testing.T) { ge.On("L1Oracle").Return(lm) feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) - + feeEstimatorConfig.On( + "ModifyGasPriceComponents", + mock.AnythingOfType("context.backgroundCtx"), + mock.AnythingOfType("*big.Int"), + mock.AnythingOfType("*big.Int"), + ).Return(func(ctx context.Context, x, y *big.Int) (*big.Int, *big.Int, error) { + return x, y, nil + }) maxGasPrice := big.NewInt(1e8) c10r, err := factory.NewCommitStoreReader(lggr, factory.NewEvmVersionFinder(), ccipcalc.EvmAddrToGeneric(addr), ec, lp, feeEstimatorConfig) // ge, maxGasPrice require.NoError(t, err) @@ -372,8 +379,10 @@ func TestCommitStoreReaders(t *testing.T) { require.NoError(t, err) assert.Equal(t, commonOffchain, c2) // We should be able to query for gas prices now. + gpe, err := cr.GasPriceEstimator(ctx) require.NoError(t, err) + gp, err := gpe.GetGasPrice(context.Background()) require.NoError(t, err) assert.True(t, gp.Cmp(big.NewInt(0)) > 0) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/fee_estimator_config.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/fee_estimator_config.go index a1a9370528..0b4d1ce75a 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/fee_estimator_config.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/fee_estimator_config.go @@ -2,8 +2,10 @@ package ccipdata import ( "context" + "math/big" ) type FeeEstimatorConfigReader interface { GetDataAvailabilityConfig(ctx context.Context) (destDAOverheadGas, destGasPerDAByte, destDAMultiplierBps int64, err error) + ModifyGasPriceComponents(ctx context.Context, execGasPrice, daGasPrice *big.Int) (modExecGasPrice, modDAGasPrice *big.Int, err error) } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks/fee_estimator_config_mock.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks/fee_estimator_config_mock.go index b19f4cd882..e62cdec87a 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks/fee_estimator_config_mock.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks/fee_estimator_config_mock.go @@ -3,6 +3,8 @@ package mocks import ( + big "math/big" + context "context" mock "github.com/stretchr/testify/mock" @@ -91,6 +93,75 @@ func (_c *FeeEstimatorConfigReader_GetDataAvailabilityConfig_Call) RunAndReturn( return _c } +// ModifyGasPriceComponents provides a mock function with given fields: ctx, execGasPrice, daGasPrice +func (_m *FeeEstimatorConfigReader) ModifyGasPriceComponents(ctx context.Context, execGasPrice *big.Int, daGasPrice *big.Int) (*big.Int, *big.Int, error) { + ret := _m.Called(ctx, execGasPrice, daGasPrice) + + if len(ret) == 0 { + panic("no return value specified for ModifyGasPriceComponents") + } + + var r0 *big.Int + var r1 *big.Int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int, *big.Int) (*big.Int, *big.Int, error)); ok { + return rf(ctx, execGasPrice, daGasPrice) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int, *big.Int) *big.Int); ok { + r0 = rf(ctx, execGasPrice, daGasPrice) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int, *big.Int) *big.Int); ok { + r1 = rf(ctx, execGasPrice, daGasPrice) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*big.Int) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, *big.Int, *big.Int) error); ok { + r2 = rf(ctx, execGasPrice, daGasPrice) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// FeeEstimatorConfigReader_ModifyGasPriceComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ModifyGasPriceComponents' +type FeeEstimatorConfigReader_ModifyGasPriceComponents_Call struct { + *mock.Call +} + +// ModifyGasPriceComponents is a helper method to define mock.On call +// - ctx context.Context +// - execGasPrice *big.Int +// - daGasPrice *big.Int +func (_e *FeeEstimatorConfigReader_Expecter) ModifyGasPriceComponents(ctx interface{}, execGasPrice interface{}, daGasPrice interface{}) *FeeEstimatorConfigReader_ModifyGasPriceComponents_Call { + return &FeeEstimatorConfigReader_ModifyGasPriceComponents_Call{Call: _e.mock.On("ModifyGasPriceComponents", ctx, execGasPrice, daGasPrice)} +} + +func (_c *FeeEstimatorConfigReader_ModifyGasPriceComponents_Call) Run(run func(ctx context.Context, execGasPrice *big.Int, daGasPrice *big.Int)) *FeeEstimatorConfigReader_ModifyGasPriceComponents_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int), args[2].(*big.Int)) + }) + return _c +} + +func (_c *FeeEstimatorConfigReader_ModifyGasPriceComponents_Call) Return(modExecGasPrice *big.Int, modDAGasPrice *big.Int, err error) *FeeEstimatorConfigReader_ModifyGasPriceComponents_Call { + _c.Call.Return(modExecGasPrice, modDAGasPrice, err) + return _c +} + +func (_c *FeeEstimatorConfigReader_ModifyGasPriceComponents_Call) RunAndReturn(run func(context.Context, *big.Int, *big.Int) (*big.Int, *big.Int, error)) *FeeEstimatorConfigReader_ModifyGasPriceComponents_Call { + _c.Call.Return(run) + return _c +} + // NewFeeEstimatorConfigReader creates a new instance of FeeEstimatorConfigReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFeeEstimatorConfigReader(t interface { diff --git a/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go b/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go index b27494ad60..34239398c8 100644 --- a/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go +++ b/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go @@ -43,6 +43,7 @@ func (g DAGasPriceEstimator) GetGasPrice(ctx context.Context) (*big.Int, error) return nil, err } var gasPrice *big.Int = execGasPrice + if gasPrice.BitLen() > int(g.priceEncodingLength) { return nil, fmt.Errorf("native gas price exceeded max range %+v", gasPrice) } @@ -56,7 +57,14 @@ func (g DAGasPriceEstimator) GetGasPrice(ctx context.Context) (*big.Int, error) return nil, err } - if daGasPrice := daGasPriceWei.ToInt(); daGasPrice.Cmp(big.NewInt(0)) > 0 { + daGasPrice := daGasPriceWei.ToInt() + + gasPrice, daGasPrice, err = g.feeEstimatorConfig.ModifyGasPriceComponents(ctx, gasPrice, daGasPrice) + if err != nil { + return nil, fmt.Errorf("gasPrice modification failed: %v", err) + } + + if daGasPrice.Cmp(big.NewInt(0)) > 0 { if daGasPrice.BitLen() > int(g.priceEncodingLength) { return nil, fmt.Errorf("data availability gas price exceeded max range %+v", daGasPrice) } diff --git a/core/services/ocr2/plugins/ccip/prices/da_price_estimator_test.go b/core/services/ocr2/plugins/ccip/prices/da_price_estimator_test.go index 158f02c4f0..52ef8c3800 100644 --- a/core/services/ocr2/plugins/ccip/prices/da_price_estimator_test.go +++ b/core/services/ocr2/plugins/ccip/prices/da_price_estimator_test.go @@ -23,11 +23,13 @@ func TestDAPriceEstimator_GetGasPrice(t *testing.T) { ctx := context.Background() testCases := []struct { - name string - daGasPrice *big.Int - execGasPrice *big.Int - expPrice *big.Int - expErr bool + name string + daGasPrice *big.Int + execGasPrice *big.Int + expPrice *big.Int + modExecGasPrice *big.Int + modDAGasPrice *big.Int + expErr bool }{ { name: "base", @@ -57,6 +59,31 @@ func TestDAPriceEstimator_GetGasPrice(t *testing.T) { expPrice: encodeGasPrice(big.NewInt(1e9), big.NewInt(0)), expErr: false, }, + { + name: "execGasPrice Modified", + daGasPrice: big.NewInt(1e9), + execGasPrice: big.NewInt(0), + modExecGasPrice: big.NewInt(1), + expPrice: encodeGasPrice(big.NewInt(1e9), big.NewInt(1)), + expErr: false, + }, + { + name: "daGasPrice Modified", + daGasPrice: big.NewInt(1e9), + execGasPrice: big.NewInt(0), + modDAGasPrice: big.NewInt(1), + expPrice: encodeGasPrice(big.NewInt(1), big.NewInt(0)), + expErr: false, + }, + { + name: "daGasPrice and execGasPrice Modified", + daGasPrice: big.NewInt(1e9), + execGasPrice: big.NewInt(0), + modDAGasPrice: big.NewInt(1), + modExecGasPrice: big.NewInt(2), + expPrice: encodeGasPrice(big.NewInt(1), big.NewInt(2)), + expErr: false, + }, { name: "price out of bounds", daGasPrice: new(big.Int).Lsh(big.NewInt(1), daGasPriceEncodingLength), @@ -74,10 +101,25 @@ func TestDAPriceEstimator_GetGasPrice(t *testing.T) { l1Oracle := mocks.NewL1Oracle(t) l1Oracle.On("GasPrice", ctx).Return(assets.NewWei(tc.daGasPrice), nil) + feeEstimatorConfig := ccipdatamocks.NewFeeEstimatorConfigReader(t) + + modRespExecGasPrice := tc.execGasPrice + if tc.modExecGasPrice != nil { + modRespExecGasPrice = tc.modExecGasPrice + } + + modRespDAGasPrice := tc.daGasPrice + if tc.modDAGasPrice != nil { + modRespDAGasPrice = tc.modDAGasPrice + } + feeEstimatorConfig.On("ModifyGasPriceComponents", mock.Anything, tc.execGasPrice, tc.daGasPrice). + Return(modRespExecGasPrice, modRespDAGasPrice, nil) + g := DAGasPriceEstimator{ execEstimator: execEstimator, l1Oracle: l1Oracle, priceEncodingLength: daGasPriceEncodingLength, + feeEstimatorConfig: feeEstimatorConfig, } gasPrice, err := g.GetGasPrice(ctx) diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 85cb3486ca..ae5244f5f0 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -12,6 +12,8 @@ import ( "sync" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipcommit" @@ -409,6 +411,16 @@ func (r *Relayer) NewCCIPCommitProvider(rargs commontypes.RelayArgs, pargs commo feeEstimatorConfig := estimatorconfig.NewFeeEstimatorConfigService() + // CCIPCommit reads only when source chain is Mantle, then reports to dest chain + // to minimize misconfigure risk, might make sense to wire Mantle only when Commit + Mantle + IsSourceProvider + if r.chain.Config().EVM().ChainType() == chaintype.ChainMantle && commitPluginConfig.IsSourceProvider { + mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client()) + if iErr != nil { + return nil, iErr + } + feeEstimatorConfig.AddGasPriceInterceptor(mantleInterceptor) + } + // The src chain implementation of this provider does not need a configWatcher or contractTransmitter; // bail early. if commitPluginConfig.IsSourceProvider { @@ -480,6 +492,16 @@ func (r *Relayer) NewCCIPExecProvider(rargs commontypes.RelayArgs, pargs commont feeEstimatorConfig := estimatorconfig.NewFeeEstimatorConfigService() + // CCIPExec reads when dest chain is mantle, and uses it to calc boosting in batching + // to minimize misconfigure risk, make sense to wire Mantle only when Exec + Mantle + !IsSourceProvider + if r.chain.Config().EVM().ChainType() == chaintype.ChainMantle && !execPluginConfig.IsSourceProvider { + mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client()) + if iErr != nil { + return nil, iErr + } + feeEstimatorConfig.AddGasPriceInterceptor(mantleInterceptor) + } + // The src chain implementation of this provider does not need a configWatcher or contractTransmitter; // bail early. if execPluginConfig.IsSourceProvider { diff --git a/core/services/relay/evm/interceptors/mantle/interceptor.go b/core/services/relay/evm/interceptors/mantle/interceptor.go new file mode 100644 index 0000000000..8d5d3bb6c2 --- /dev/null +++ b/core/services/relay/evm/interceptors/mantle/interceptor.go @@ -0,0 +1,80 @@ +package mantle + +import ( + "context" + "fmt" + "math/big" + "strings" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + evmClient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" +) + +const ( + // tokenRatio is not volatile and can be requested not often. + tokenRatioUpdateInterval = 60 * time.Minute + // tokenRatio fetches the tokenRatio used for Mantle's gas price calculation + tokenRatioMethod = "tokenRatio" + mantleTokenRatioAbiString = `[{"inputs":[],"name":"tokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` +) + +type Interceptor struct { + client evmClient.Client + tokenRatioCallData []byte + tokenRatio *big.Int + tokenRatioLastUpdate time.Time +} + +func NewInterceptor(_ context.Context, client evmClient.Client) (*Interceptor, error) { + // Encode calldata for tokenRatio method + tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(mantleTokenRatioAbiString)) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for Mantle; %v", tokenRatioMethod, err) + } + tokenRatioCallData, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for Mantle; %v", tokenRatioMethod, err) + } + + return &Interceptor{ + client: client, + tokenRatioCallData: tokenRatioCallData, + }, nil +} + +// ModifyGasPriceComponents returns modified gasPrice. +func (i *Interceptor) ModifyGasPriceComponents(ctx context.Context, execGasPrice, daGasPrice *big.Int) (*big.Int, *big.Int, error) { + if time.Since(i.tokenRatioLastUpdate) > tokenRatioUpdateInterval { + mantleTokenRatio, err := i.getMantleTokenRatio(ctx) + if err != nil { + return nil, nil, err + } + + i.tokenRatio, i.tokenRatioLastUpdate = mantleTokenRatio, time.Now() + } + + // multiply daGasPrice and execGas price by tokenRatio + newExecGasPrice := new(big.Int).Mul(execGasPrice, i.tokenRatio) + newDAGasPrice := new(big.Int).Mul(daGasPrice, i.tokenRatio) + return newExecGasPrice, newDAGasPrice, nil +} + +// getMantleTokenRatio Requests and returns a token ratio value for the Mantle chain. +func (i *Interceptor) getMantleTokenRatio(ctx context.Context) (*big.Int, error) { + precompile := common.HexToAddress(rollups.OPGasOracleAddress) + tokenRatio, err := i.client.CallContract(ctx, ethereum.CallMsg{ + To: &precompile, + Data: i.tokenRatioCallData, + }, nil) + + if err != nil { + return nil, fmt.Errorf("getMantleTokenRatio call failed: %w", err) + } + + return new(big.Int).SetBytes(tokenRatio), nil +} diff --git a/core/services/relay/evm/interceptors/mantle/interceptor_test.go b/core/services/relay/evm/interceptors/mantle/interceptor_test.go new file mode 100644 index 0000000000..9134d996c2 --- /dev/null +++ b/core/services/relay/evm/interceptors/mantle/interceptor_test.go @@ -0,0 +1,96 @@ +package mantle + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client/mocks" +) + +func TestInterceptor(t *testing.T) { + ethClient := mocks.NewClient(t) + ctx := context.Background() + + tokenRatio := big.NewInt(10) + interceptor, err := NewInterceptor(ctx, ethClient) + require.NoError(t, err) + + // request token ratio + ethClient.On("CallContract", ctx, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})). + Return(common.BigToHash(tokenRatio).Bytes(), nil).Once() + + modExecGasPrice, modDAGasPrice, err := interceptor.ModifyGasPriceComponents(ctx, big.NewInt(1), big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, int64(10), modExecGasPrice.Int64()) + require.Equal(t, int64(10), modDAGasPrice.Int64()) + + // second call won't invoke eth client + modExecGasPrice, modDAGasPrice, err = interceptor.ModifyGasPriceComponents(ctx, big.NewInt(2), big.NewInt(1)) + require.NoError(t, err) + require.Equal(t, int64(20), modExecGasPrice.Int64()) + require.Equal(t, int64(10), modDAGasPrice.Int64()) +} + +func TestModifyGasPriceComponents(t *testing.T) { + testCases := map[string]struct { + execGasPrice *big.Int + daGasPrice *big.Int + tokenRatio *big.Int + resultExecGasPrice *big.Int + resultDAGasPrice *big.Int + }{ + "regular": { + execGasPrice: big.NewInt(1000), + daGasPrice: big.NewInt(100), + resultExecGasPrice: big.NewInt(2000), + resultDAGasPrice: big.NewInt(200), + tokenRatio: big.NewInt(2), + }, + "zero DAGasPrice": { + execGasPrice: big.NewInt(1000), + daGasPrice: big.NewInt(0), + resultExecGasPrice: big.NewInt(5000), + resultDAGasPrice: big.NewInt(0), + tokenRatio: big.NewInt(5), + }, + "zero ExecGasPrice": { + execGasPrice: big.NewInt(0), + daGasPrice: big.NewInt(10), + resultExecGasPrice: big.NewInt(0), + resultDAGasPrice: big.NewInt(50), + tokenRatio: big.NewInt(5), + }, + "zero token ratio": { + execGasPrice: big.NewInt(15), + daGasPrice: big.NewInt(10), + resultExecGasPrice: big.NewInt(0), + resultDAGasPrice: big.NewInt(0), + tokenRatio: big.NewInt(0), + }, + } + + for tcName, tc := range testCases { + t.Run(tcName, func(t *testing.T) { + ethClient := mocks.NewClient(t) + ctx := context.Background() + + interceptor, err := NewInterceptor(ctx, ethClient) + require.NoError(t, err) + + // request token ratio + ethClient.On("CallContract", ctx, mock.IsType(ethereum.CallMsg{}), mock.IsType(&big.Int{})). + Return(common.BigToHash(tc.tokenRatio).Bytes(), nil).Once() + + modExecGasPrice, modDAGasPrice, err := interceptor.ModifyGasPriceComponents(ctx, tc.execGasPrice, tc.daGasPrice) + require.NoError(t, err) + require.Equal(t, tc.resultExecGasPrice.Int64(), modExecGasPrice.Int64()) + require.Equal(t, tc.resultDAGasPrice.Int64(), modDAGasPrice.Int64()) + }) + } +} From b664c369d21f46a3338a0a72fc4183ffdea03f06 Mon Sep 17 00:00:00 2001 From: "valerii.kabisov" Date: Fri, 4 Oct 2024 01:03:22 +0900 Subject: [PATCH 20/31] Revert "Bump version for CCIP 2.14.0-ccip1.5.3" This reverts commit da80ec5525f47e316e22614d320884f88e1f61e0. --- contracts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/package.json b/contracts/package.json index efbf79e8ef..334b75701c 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@chainlink/contracts-ccip", - "version": "2.14.0-ccip1.5.3", + "version": "1.5.0", "description": "Chainlink CCIP smart contracts", "author": "Chainlink devs", "license": "BUSL-1.1", From 0d172d1dcee475f8b23afb01f9f915ceda4e1093 Mon Sep 17 00:00:00 2001 From: "valerii.kabisov" Date: Fri, 4 Oct 2024 01:05:58 +0900 Subject: [PATCH 21/31] Bump version for CCIP 2.14.0-ccip1.5.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e9931ed7cd..2397c5ee5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ccip", - "version": "2.14.0-ccip1.5.2", + "version": "2.14.0-ccip1.5.4", "description": "node of the decentralized oracle network, bridging on and off-chain computation", "main": "index.js", "scripts": { From 943e01114d226e1c1b5494ecbf8bf5644fa17b5e Mon Sep 17 00:00:00 2001 From: Sishir Giri Date: Mon, 26 Aug 2024 09:51:45 -0700 Subject: [PATCH 22/31] Chore: Change metis config (#1345) ## Motivation ## Solution --- core/chains/evm/config/toml/defaults/Metis_Sepolia.toml | 2 +- docs/CONFIG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/chains/evm/config/toml/defaults/Metis_Sepolia.toml b/core/chains/evm/config/toml/defaults/Metis_Sepolia.toml index 286b888e1a..4ff4056c75 100644 --- a/core/chains/evm/config/toml/defaults/Metis_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Metis_Sepolia.toml @@ -1,5 +1,5 @@ ChainID = '59902' -ChainType = 'metis' +ChainType = 'optimismBedrock' FinalityDepth = 10 FinalityTagEnabled = true MinIncomingConfirmations = 1 diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 52ff148c6c..7c112c6d1a 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -7067,7 +7067,7 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'metis' +ChainType = 'optimismBedrock' FinalityDepth = 10 FinalityTagEnabled = true LogBackfillBatchSize = 1000 From 28e5feccd5583b5669657dcaa0c9dba0c4c753b1 Mon Sep 17 00:00:00 2001 From: Sishir Giri Date: Thu, 3 Oct 2024 23:29:22 -0700 Subject: [PATCH 23/31] Chore: Metis change (#1485) ## Motivation ## Solution --- core/chains/evm/config/toml/defaults/Metis_Mainnet.toml | 2 +- docs/CONFIG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml b/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml index f057400d01..388b9f00b0 100644 --- a/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Metis_Mainnet.toml @@ -1,6 +1,6 @@ # Metis is an L2 chain based on Optimism. ChainID = '1088' -ChainType = 'metis' +ChainType = 'optimismBedrock' # Sequencer offers absolute finality FinalityDepth = 10 FinalityTagEnabled = true diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 7c112c6d1a..f27f092e4e 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -4511,7 +4511,7 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'metis' +ChainType = 'optimismBedrock' FinalityDepth = 10 FinalityTagEnabled = true LogBackfillBatchSize = 1000 From 047109ffdec8cbe806ac761340503b11158bcaac Mon Sep 17 00:00:00 2001 From: Valerii Kabisov <172247313+valerii-kabisov-cll@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:58:42 +0900 Subject: [PATCH 24/31] Do not return the error in case if onRamp not initialized (#1487) ## Motivation For the exec provider, in some cases, the onRamp reader is not initialized. For immediate reaction, we need to allow execution without blocking in cases if onRamp is not initialized. It fails due the issue when onRampReader is not initialized for execProvider. This fix should fix this issue. In case the onRamp reader is not initialized, the module will return 0 values and DAGasEstimator will behave as it worked before this feature been implemented. --- .../services/ocr2/plugins/ccip/estimatorconfig/config.go | 3 +-- .../ocr2/plugins/ccip/estimatorconfig/config_test.go | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/config.go b/core/services/ocr2/plugins/ccip/estimatorconfig/config.go index 0e66f4fb39..4737bd33e8 100644 --- a/core/services/ocr2/plugins/ccip/estimatorconfig/config.go +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/config.go @@ -2,7 +2,6 @@ package estimatorconfig import ( "context" - "errors" "math/big" "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" @@ -42,7 +41,7 @@ func (c *FeeEstimatorConfigService) SetOnRampReader(reader ccip.OnRampReader) { // GetDynamicConfig should be cached in the onRamp reader to avoid unnecessary on-chain calls func (c *FeeEstimatorConfigService) GetDataAvailabilityConfig(ctx context.Context) (destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps int64, err error) { if c.onRampReader == nil { - return 0, 0, 0, errors.New("no OnRampReader has been configured") + return 0, 0, 0, nil } cfg, err := c.onRampReader.GetDynamicConfig(ctx) diff --git a/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go b/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go index 0ed7510591..3ecd88ae3b 100644 --- a/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go +++ b/core/services/ocr2/plugins/ccip/estimatorconfig/config_test.go @@ -24,8 +24,11 @@ func TestFeeEstimatorConfigService(t *testing.T) { var expectedDestDataAvailabilityMultiplierBps int64 = 3 onRampReader := mocks.NewOnRampReader(t) - _, _, _, err := svc.GetDataAvailabilityConfig(ctx) - require.Error(t, err) + destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps, err := svc.GetDataAvailabilityConfig(ctx) + require.NoError(t, err) // if onRampReader not set, return nil error and 0 values + require.EqualValues(t, 0, destDataAvailabilityOverheadGas) + require.EqualValues(t, 0, destGasPerDataAvailabilityByte) + require.EqualValues(t, 0, destDataAvailabilityMultiplierBps) svc.SetOnRampReader(onRampReader) onRampReader.On("GetDynamicConfig", ctx). @@ -35,7 +38,7 @@ func TestFeeEstimatorConfigService(t *testing.T) { DestDataAvailabilityMultiplierBps: uint16(expectedDestDataAvailabilityMultiplierBps), }, nil).Once() - destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps, err := svc.GetDataAvailabilityConfig(ctx) + destDataAvailabilityOverheadGas, destGasPerDataAvailabilityByte, destDataAvailabilityMultiplierBps, err = svc.GetDataAvailabilityConfig(ctx) require.NoError(t, err) require.Equal(t, expectedDestDataAvailabilityOverheadGas, destDataAvailabilityOverheadGas) require.Equal(t, expectedDestGasPerDataAvailabilityByte, destGasPerDataAvailabilityByte) From 890450dd8f631f1502e4f85c0d2a2cc155695968 Mon Sep 17 00:00:00 2001 From: Chunkai Yang Date: Sun, 6 Oct 2024 01:19:37 -0400 Subject: [PATCH 25/31] use chain Id as opposed to chain type to identify Mantle (#1489) ## Motivation ## Solution --- core/services/relay/evm/evm.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index ae5244f5f0..f2143ceded 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -12,7 +12,7 @@ import ( "sync" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/estimatorconfig/interceptors/mantle" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" @@ -413,12 +413,14 @@ func (r *Relayer) NewCCIPCommitProvider(rargs commontypes.RelayArgs, pargs commo // CCIPCommit reads only when source chain is Mantle, then reports to dest chain // to minimize misconfigure risk, might make sense to wire Mantle only when Commit + Mantle + IsSourceProvider - if r.chain.Config().EVM().ChainType() == chaintype.ChainMantle && commitPluginConfig.IsSourceProvider { - mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client()) - if iErr != nil { - return nil, iErr + if r.chain.Config().EVM().ChainID().Uint64() == 5003 || r.chain.Config().EVM().ChainID().Uint64() == 5000 { + if commitPluginConfig.IsSourceProvider { + mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client()) + if iErr != nil { + return nil, iErr + } + feeEstimatorConfig.AddGasPriceInterceptor(mantleInterceptor) } - feeEstimatorConfig.AddGasPriceInterceptor(mantleInterceptor) } // The src chain implementation of this provider does not need a configWatcher or contractTransmitter; @@ -494,12 +496,14 @@ func (r *Relayer) NewCCIPExecProvider(rargs commontypes.RelayArgs, pargs commont // CCIPExec reads when dest chain is mantle, and uses it to calc boosting in batching // to minimize misconfigure risk, make sense to wire Mantle only when Exec + Mantle + !IsSourceProvider - if r.chain.Config().EVM().ChainType() == chaintype.ChainMantle && !execPluginConfig.IsSourceProvider { - mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client()) - if iErr != nil { - return nil, iErr + if r.chain.Config().EVM().ChainID().Uint64() == 5003 || r.chain.Config().EVM().ChainID().Uint64() == 5000 { + if !execPluginConfig.IsSourceProvider { + mantleInterceptor, iErr := mantle.NewInterceptor(ctx, r.chain.Client()) + if iErr != nil { + return nil, iErr + } + feeEstimatorConfig.AddGasPriceInterceptor(mantleInterceptor) } - feeEstimatorConfig.AddGasPriceInterceptor(mantleInterceptor) } // The src chain implementation of this provider does not need a configWatcher or contractTransmitter; From dbe65890c38c91b741d29f2249d9f7d16aced60d Mon Sep 17 00:00:00 2001 From: Juan Lautaro Fernandez Date: Tue, 8 Oct 2024 18:22:30 -0300 Subject: [PATCH 26/31] Bump chainselectors cherrypicked (#1493) Bumps chain-selectors version to latest. cherry picked from https://github.com/smartcontractkit/ccip/pull/1492 ## Motivation ## Solution --------- Co-authored-by: Mohamed Mehany <7327188+mohamed-mehany@users.noreply.github.com> --- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 0fdab93d49..65ee57c32a 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -22,7 +22,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/shopspring/decimal v1.4.0 - github.com/smartcontractkit/chain-selectors v1.0.23 + github.com/smartcontractkit/chain-selectors v1.0.25 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 42385b4041..ccf30e4863 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1066,8 +1066,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= -github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.25 h1:TrcpbCsDRJ1322ukdi64KBou4aabPnQ1OYfQMFMrjcg= +github.com/smartcontractkit/chain-selectors v1.0.25/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/go.mod b/go.mod index 92fb1c4306..6647161723 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/scylladb/go-reflectx v1.0.1 github.com/shirou/gopsutil/v3 v3.24.3 github.com/shopspring/decimal v1.4.0 - github.com/smartcontractkit/chain-selectors v1.0.23 + github.com/smartcontractkit/chain-selectors v1.0.25 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 diff --git a/go.sum b/go.sum index 324c4f9ec7..82e8608def 100644 --- a/go.sum +++ b/go.sum @@ -1023,8 +1023,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= -github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.25 h1:TrcpbCsDRJ1322ukdi64KBou4aabPnQ1OYfQMFMrjcg= +github.com/smartcontractkit/chain-selectors v1.0.25/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index b2632668f1..8963d3c58b 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -34,7 +34,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685 - github.com/smartcontractkit/chain-selectors v1.0.23 + github.com/smartcontractkit/chain-selectors v1.0.25 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index ceb9cacd73..e414b10d9e 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1390,8 +1390,8 @@ github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685 h1:jakAsdhDxV4cMgRAcSvHraXjyePi8umG5SEUTGFvuy8= github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685/go.mod h1:p7L/xNEQpHDdZtgFA6/FavuZHqvV3kYhQysxBywmq1k= -github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= -github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.25 h1:TrcpbCsDRJ1322ukdi64KBou4aabPnQ1OYfQMFMrjcg= +github.com/smartcontractkit/chain-selectors v1.0.25/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index b89026ac9b..b4e1f2a4d0 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -374,7 +374,7 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smartcontractkit/chain-selectors v1.0.23 // indirect + github.com/smartcontractkit/chain-selectors v1.0.25 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 1ab5063462..5dd303b8b6 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1354,8 +1354,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ= github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= -github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= -github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.25 h1:TrcpbCsDRJ1322ukdi64KBou4aabPnQ1OYfQMFMrjcg= +github.com/smartcontractkit/chain-selectors v1.0.25/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= From 5b3e0c257d5c044ec32cec0801f24113d86fd1e3 Mon Sep 17 00:00:00 2001 From: Matt Yang Date: Thu, 10 Oct 2024 11:13:13 -0400 Subject: [PATCH 27/31] bump package version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2397c5ee5c..907a5bb311 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ccip", - "version": "2.14.0-ccip1.5.4", + "version": "2.14.0-ccip1.5.5", "description": "node of the decentralized oracle network, bridging on and off-chain computation", "main": "index.js", "scripts": { From 77116838585a6cf6bab9b23443a9f13a2e2e4fc6 Mon Sep 17 00:00:00 2001 From: Simson Date: Tue, 15 Oct 2024 20:36:51 +0530 Subject: [PATCH 28/31] updated chain-selectors to 1.0.27 (#1500) (#1502) (cherry picked from commit 4377d9ab5cfd40622a1d2f21ca5527f76d5daabb) ## Motivation ## Solution --- core/scripts/ccip/manual-execution/go.mod | 2 +- core/scripts/ccip/manual-execution/go.sum | 4 ++-- core/scripts/go.mod | 2 +- core/scripts/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/load/go.mod | 2 +- integration-tests/load/go.sum | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/scripts/ccip/manual-execution/go.mod b/core/scripts/ccip/manual-execution/go.mod index ec295ec868..356cf7d8dc 100644 --- a/core/scripts/ccip/manual-execution/go.mod +++ b/core/scripts/ccip/manual-execution/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/ethereum/go-ethereum v1.11.3 github.com/pkg/errors v0.9.1 - github.com/smartcontractkit/chain-selectors v1.0.23 + github.com/smartcontractkit/chain-selectors v1.0.27 go.uber.org/multierr v1.1.0 golang.org/x/crypto v0.1.0 ) diff --git a/core/scripts/ccip/manual-execution/go.sum b/core/scripts/ccip/manual-execution/go.sum index a5bf3ee297..1e38a8a5e0 100644 --- a/core/scripts/ccip/manual-execution/go.sum +++ b/core/scripts/ccip/manual-execution/go.sum @@ -70,8 +70,8 @@ github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/smartcontractkit/chain-selectors v1.0.23 h1:D2Eaex4Cw/O7Lg3tX6WklOqnjjIQAEBnutCtksPzVDY= -github.com/smartcontractkit/chain-selectors v1.0.23/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k= +github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 65ee57c32a..2a8aaff9dd 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -22,7 +22,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/shopspring/decimal v1.4.0 - github.com/smartcontractkit/chain-selectors v1.0.25 + github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index ccf30e4863..38c63819f8 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1066,8 +1066,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chain-selectors v1.0.25 h1:TrcpbCsDRJ1322ukdi64KBou4aabPnQ1OYfQMFMrjcg= -github.com/smartcontractkit/chain-selectors v1.0.25/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k= +github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/go.mod b/go.mod index 6647161723..af1174beab 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/scylladb/go-reflectx v1.0.1 github.com/shirou/gopsutil/v3 v3.24.3 github.com/shopspring/decimal v1.4.0 - github.com/smartcontractkit/chain-selectors v1.0.25 + github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 diff --git a/go.sum b/go.sum index 82e8608def..b81f4930a0 100644 --- a/go.sum +++ b/go.sum @@ -1023,8 +1023,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chain-selectors v1.0.25 h1:TrcpbCsDRJ1322ukdi64KBou4aabPnQ1OYfQMFMrjcg= -github.com/smartcontractkit/chain-selectors v1.0.25/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k= +github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 8963d3c58b..c26b3d27e9 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -34,7 +34,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685 - github.com/smartcontractkit/chain-selectors v1.0.25 + github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index e414b10d9e..381abac5be 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1390,8 +1390,8 @@ github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685 h1:jakAsdhDxV4cMgRAcSvHraXjyePi8umG5SEUTGFvuy8= github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240808195812-ae0378684685/go.mod h1:p7L/xNEQpHDdZtgFA6/FavuZHqvV3kYhQysxBywmq1k= -github.com/smartcontractkit/chain-selectors v1.0.25 h1:TrcpbCsDRJ1322ukdi64KBou4aabPnQ1OYfQMFMrjcg= -github.com/smartcontractkit/chain-selectors v1.0.25/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k= +github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index b4e1f2a4d0..d086677434 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -374,7 +374,7 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smartcontractkit/chain-selectors v1.0.25 // indirect + github.com/smartcontractkit/chain-selectors v1.0.27 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240718160222-2dc0c8136bfa // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240712132946-267a37c5ac6e // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 5dd303b8b6..b181bfbaae 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1354,8 +1354,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.12.2 h1:x3OppyMyGIbbiyFhsBmpf9pwkUzMhthJMRNmNlA4LaQ= github.com/slack-go/slack v0.12.2/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= -github.com/smartcontractkit/chain-selectors v1.0.25 h1:TrcpbCsDRJ1322ukdi64KBou4aabPnQ1OYfQMFMrjcg= -github.com/smartcontractkit/chain-selectors v1.0.25/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= +github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+30iWKL/sWq8uyiLHM8k= +github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8umfIfVVlwC7+n5izbLSFgjw8= github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240816163757-48726fd8165f h1:lQZBOjeYFpCdk0mGQUhbrJipd00tu49xK4zSijC/9Co= From a917e9a3fcb44859706726e51dc7519d1890c06c Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:48:19 -0500 Subject: [PATCH 29/31] Fix Nil pointer error in TXM stuck tx detector (CCIP 1.5) (#1499) An NPE bug was identified in the Stuck Tx Detector component. The fix was addressed in this https://github.com/smartcontractkit/chainlink/pull/14685 in core but needs to be applied to CCIP as well. Changes could not be cherry-picked because of other changes made to the component in between so changes were back ported manually. --- core/chains/evm/txmgr/stuck_tx_detector.go | 23 +++++++++++++--- .../evm/txmgr/stuck_tx_detector_test.go | 26 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/core/chains/evm/txmgr/stuck_tx_detector.go b/core/chains/evm/txmgr/stuck_tx_detector.go index 362bb6c0a5..d756e95058 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector.go +++ b/core/chains/evm/txmgr/stuck_tx_detector.go @@ -215,6 +215,20 @@ func (d *stuckTxDetector) detectStuckTransactionsHeuristic(ctx context.Context, } // Tx attempts are loaded from newest to oldest oldestBroadcastAttempt, newestBroadcastAttempt, broadcastedAttemptsCount := findBroadcastedAttempts(tx) + d.lggr.Debugf("found %d broadcasted attempts for tx id %d in stuck transaction heuristic", broadcastedAttemptsCount, tx.ID) + + // attempt shouldn't be nil as we validated in FindUnconfirmedTxWithLowestNonce, but added anyway for a "belts and braces" approach + if oldestBroadcastAttempt == nil || newestBroadcastAttempt == nil { + d.lggr.Debugw("failed to find broadcast attempt for tx in stuck transaction heuristic", "tx", tx) + continue + } + + // sanity check + if oldestBroadcastAttempt.BroadcastBeforeBlockNum == nil { + d.lggr.Debugw("BroadcastBeforeBlockNum was not set for broadcast attempt in stuck transaction heuristic", "attempt", oldestBroadcastAttempt) + continue + } + // 2. Check if Threshold amount of blocks have passed since the oldest attempt's broadcast block num if *oldestBroadcastAttempt.BroadcastBeforeBlockNum > blockNum-int64(*d.cfg.Threshold()) { continue @@ -244,17 +258,18 @@ func compareGasFees(attemptGas gas.EvmFee, marketGas gas.EvmFee) int { } // Assumes tx attempts are loaded newest to oldest -func findBroadcastedAttempts(tx Tx) (oldestAttempt TxAttempt, newestAttempt TxAttempt, broadcastedCount uint32) { +func findBroadcastedAttempts(tx Tx) (oldestAttempt *TxAttempt, newestAttempt *TxAttempt, broadcastedCount uint32) { foundNewest := false - for _, attempt := range tx.TxAttempts { + for i := range tx.TxAttempts { + attempt := tx.TxAttempts[i] if attempt.State != types.TxAttemptBroadcast { continue } if !foundNewest { - newestAttempt = attempt + newestAttempt = &attempt foundNewest = true } - oldestAttempt = attempt + oldestAttempt = &attempt broadcastedCount++ } return diff --git a/core/chains/evm/txmgr/stuck_tx_detector_test.go b/core/chains/evm/txmgr/stuck_tx_detector_test.go index eb22830ef3..c653d297e8 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector_test.go +++ b/core/chains/evm/txmgr/stuck_tx_detector_test.go @@ -278,6 +278,15 @@ func TestStuckTxDetector_DetectStuckTransactionsHeuristic(t *testing.T) { require.NoError(t, err) require.Len(t, txs, 1) }) + + t.Run("detects stuck transaction with empty BroadcastBeforeBlockNum in attempts will be skipped without panic", func(t *testing.T) { + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + enabledAddresses := []common.Address{fromAddress} + mustInsertUnconfirmedTxWithBroadcastAttemptsContainsEmptyBroadcastBeforeBlockNum(t, txStore, 0, fromAddress, autoPurgeMinAttempts, marketGasPrice.Add(oneGwei)) + txs, err := stuckTxDetector.DetectStuckTransactions(ctx, enabledAddresses, blockNum) + require.NoError(t, err) + require.Len(t, txs, 0) + }) } func TestStuckTxDetector_DetectStuckTransactionsZkEVM(t *testing.T) { @@ -435,6 +444,23 @@ func mustInsertUnconfirmedTxWithBroadcastAttempts(t *testing.T, txStore txmgr.Te return etx } +// helper function for edge case where broadcast attempt contains empty pointer +func mustInsertUnconfirmedTxWithBroadcastAttemptsContainsEmptyBroadcastBeforeBlockNum(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, numAttempts uint32, latestGasPrice *assets.Wei) txmgr.Tx { + ctx := tests.Context(t) + etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, nonce, fromAddress) + // Insert attempts from oldest to newest + for i := int64(numAttempts - 1); i >= 0; i-- { + attempt := cltest.NewLegacyEthTxAttempt(t, etx.ID) + attempt.State = txmgrtypes.TxAttemptBroadcast + attempt.BroadcastBeforeBlockNum = nil + attempt.TxFee = gas.EvmFee{Legacy: latestGasPrice.Sub(assets.NewWeiI(i))} + require.NoError(t, txStore.InsertTxAttempt(ctx, &attempt)) + } + etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) + require.NoError(t, err) + return etx +} + func mustInsertFatalErrorTxWithError(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, blockNum int64) txmgr.Tx { etx := cltest.NewEthTx(fromAddress) etx.State = txmgrcommon.TxFatalError From 909ecdfbc165ab17a32de2c106fb665429d43db0 Mon Sep 17 00:00:00 2001 From: Chunkai Yang Date: Mon, 21 Oct 2024 15:19:01 -0400 Subject: [PATCH 30/31] Add Mantle NonceTooLow Error (#14859) (#1505) https://github.com/smartcontractkit/chainlink/pull/14859/files#diff-e9a4f21557894a9a2c77ca090bea14ed69bdfea02500c0cfd04628a1941f6e6c * handle mantle "nonce too low" error * add changeset ## Motivation ## Solution Co-authored-by: Matthew Romage <33700623+ma33r@users.noreply.github.com> --- .changeset/pretty-worms-smell.md | 5 +++++ core/chains/evm/client/errors.go | 1 + core/chains/evm/client/errors_test.go | 1 + 3 files changed, 7 insertions(+) create mode 100644 .changeset/pretty-worms-smell.md diff --git a/.changeset/pretty-worms-smell.md b/.changeset/pretty-worms-smell.md new file mode 100644 index 0000000000..9633de0950 --- /dev/null +++ b/.changeset/pretty-worms-smell.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Added a custom client error message for Mantle to capture NonceTooLow error. #added diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index 91b32e7642..812733b35f 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -257,6 +257,7 @@ var aStar = ClientErrors{ var mantle = ClientErrors{ InsufficientEth: regexp.MustCompile(`(: |^)'*insufficient funds for gas \* price \+ value`), Fatal: regexp.MustCompile(`(: |^)'*invalid sender`), + NonceTooLow: regexp.MustCompile(`(: |^)'*nonce too low`), } var gnosis = ClientErrors{ diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index f06cf8b135..e4a9900d4a 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -44,6 +44,7 @@ func Test_Eth_Errors(t *testing.T) { {"call failed: OldNonce, Current nonce: 22, nonce of rejected tx: 17", true, "Nethermind"}, {"nonce too low. allowed nonce range: 427 - 447, actual: 426", true, "zkSync"}, {"client error nonce too low", true, "tomlConfig"}, + {"failed to forward tx to sequencer, please try again. Error message: 'nonce too low'", true, "Mantle"}, } for _, test := range tests { From 1bb5150d68f7e215dc04421b2ee0140f709c6113 Mon Sep 17 00:00:00 2001 From: Lautaro Fernandez Date: Sat, 2 Nov 2024 00:49:25 -0300 Subject: [PATCH 31/31] adding configs and error mappings for batch 1 --- core/chains/evm/client/errors.go | 6 +++++- core/chains/evm/client/errors_test.go | 2 ++ .../chains/evm/config/toml/defaults/BOB_Testnet.toml | 12 ++++++++++++ .../evm/config/toml/defaults/Berachain_Testnet.toml | 12 ++++++++++++ .../evm/config/toml/defaults/Bsquared_Testnet.toml | 12 ++++++++++++ .../evm/config/toml/defaults/Unichain_Testnet.toml | 11 +++++++++++ .../evm/config/toml/defaults/Worldchain_Testnet.toml | 11 +++++++++++ 7 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 core/chains/evm/config/toml/defaults/BOB_Testnet.toml create mode 100644 core/chains/evm/config/toml/defaults/Berachain_Testnet.toml create mode 100644 core/chains/evm/config/toml/defaults/Bsquared_Testnet.toml create mode 100644 core/chains/evm/config/toml/defaults/Unichain_Testnet.toml create mode 100644 core/chains/evm/config/toml/defaults/Worldchain_Testnet.toml diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index 812733b35f..563ba3da32 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -254,6 +254,10 @@ var aStar = ClientErrors{ TerminallyUnderpriced: regexp.MustCompile(`(?:: |^)(gas price less than block base fee)$`), } +var berachain = ClientErrors{ + Fatal: regexp.MustCompile(`(: |^)'*invalid chain ID`), +} + var mantle = ClientErrors{ InsufficientEth: regexp.MustCompile(`(: |^)'*insufficient funds for gas \* price \+ value`), Fatal: regexp.MustCompile(`(: |^)'*invalid sender`), @@ -271,7 +275,7 @@ var internal = ClientErrors{ TerminallyStuck: regexp.MustCompile(TerminallyStuckMsg), } -var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm, mantle, aStar, gnosis, internal} +var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm, mantle, aStar, berachain, gnosis, internal} // ClientErrorRegexes returns a map of compiled regexes for each error type func ClientErrorRegexes(errsRegex config.ClientErrors) *ClientErrors { diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index e4a9900d4a..9f16a7a367 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -407,6 +407,8 @@ func Test_Eth_Errors_Fatal(t *testing.T) { {"failed to forward tx to sequencer, please try again. Error message: 'invalid sender'", true, "Mantle"}, {"client error fatal", true, "tomlConfig"}, + + {"invalid chain ID", true, "Berachain"}, } for _, test := range tests { diff --git a/core/chains/evm/config/toml/defaults/BOB_Testnet.toml b/core/chains/evm/config/toml/defaults/BOB_Testnet.toml new file mode 100644 index 0000000000..f749e5e729 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/BOB_Testnet.toml @@ -0,0 +1,12 @@ +ChainID = '808813' +FinalityDepth = 900 # compat test finality_depth: ~850 +LogPollInterval = '5s' # compat test block_time: ~0.5s + +NoNewFinalizedHeadsThreshold = '10m' # with 5s threw PLENTY of [RPC's finalized state is out of sync; no new finalized heads received for 5s (last finalized head received was 4939141)] + +FinalityTagEnabled = true #compat test get_logs>supports_finalized_tag: returned true + +[GasEstimator] +EIP1559DynamicFees = true +Mode = 'FeeHistory' + diff --git a/core/chains/evm/config/toml/defaults/Berachain_Testnet.toml b/core/chains/evm/config/toml/defaults/Berachain_Testnet.toml new file mode 100644 index 0000000000..ed71c50915 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Berachain_Testnet.toml @@ -0,0 +1,12 @@ +ChainID = '80084' +FinalityDepth = 10 # compat test finality_depth: 1 +LogPollInterval = '5s' # compat test block_time: ~1.7s + +NoNewFinalizedHeadsThreshold = '10m' # with 5s threw PLENTY of [RPC's finalized state is out of sync; no new finalized heads received for 5s (last finalized head received was 4939141)] + +FinalityTagEnabled = true #compat test get_logs>supports_finalized_tag: returned true + +[GasEstimator] +EIP1559DynamicFees = true +Mode = 'FeeHistory' + diff --git a/core/chains/evm/config/toml/defaults/Bsquared_Testnet.toml b/core/chains/evm/config/toml/defaults/Bsquared_Testnet.toml new file mode 100644 index 0000000000..2df06dafd0 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Bsquared_Testnet.toml @@ -0,0 +1,12 @@ +ChainID = '1123' +FinalityDepth = 2000 # compat test finality_depth: ~1900 +LogPollInterval = '5s' # compat test block_time: ~2s + +NoNewFinalizedHeadsThreshold = '10m' # with 10m threw PLENTY of [RPC's finalized state is out of sync; no new finalized heads received for 10m0s (last finalized head received was 3100170)] + +FinalityTagEnabled = true #compat test get_logs>supports_finalized_tag: returned true + +[GasEstimator] +EIP1559DynamicFees = true +Mode = 'FeeHistory' + diff --git a/core/chains/evm/config/toml/defaults/Unichain_Testnet.toml b/core/chains/evm/config/toml/defaults/Unichain_Testnet.toml new file mode 100644 index 0000000000..d36e921918 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Unichain_Testnet.toml @@ -0,0 +1,11 @@ +ChainID = '1301' +FinalityDepth = 2000 # compat test finality_depth: ~1900 +LogPollInterval = '5s' # compat test block_time: ~1s + +NoNewFinalizedHeadsThreshold = '10m' # not sure what to put here, from Worldchain set to 5s threw a huge amount of errors + +FinalityTagEnabled = true #compat test get_logs>supports_finalized_tag: returned true + +[GasEstimator] +EIP1559DynamicFees = true +Mode = 'FeeHistory' \ No newline at end of file diff --git a/core/chains/evm/config/toml/defaults/Worldchain_Testnet.toml b/core/chains/evm/config/toml/defaults/Worldchain_Testnet.toml new file mode 100644 index 0000000000..fb5a6d0bd9 --- /dev/null +++ b/core/chains/evm/config/toml/defaults/Worldchain_Testnet.toml @@ -0,0 +1,11 @@ +ChainID = '4801' +FinalityDepth = 2500 # compat test finality_depth: ~2400, but it also threw 4000 in other round, which is it? +LogPollInterval = '5s' # compat test block_time: ~0.5s + +NoNewFinalizedHeadsThreshold = '10m' # with 5s threw PLENTY of [RPC's finalized state is out of sync; no new finalized heads received for 5s (last finalized head received was 4939141)] + +FinalityTagEnabled = true #compat test get_logs>supports_finalized_tag: returned true + +[GasEstimator] +EIP1559DynamicFees = true +Mode = 'FeeHistory'