diff --git a/.changeset/soft-rivers-care.md b/.changeset/soft-rivers-care.md new file mode 100644 index 00000000000..22eeea042ba --- /dev/null +++ b/.changeset/soft-rivers-care.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +interface change for plugin to support extra args codec, right now noop #added diff --git a/.github/actions/golangci-lint/action.yml b/.github/actions/golangci-lint/action.yml index 8fa3e1775d0..5fb7865c5e8 100644 --- a/.github/actions/golangci-lint/action.yml +++ b/.github/actions/golangci-lint/action.yml @@ -18,6 +18,11 @@ inputs: description: Set where the go module file is located at default: "go.sum" +outputs: + golang-report-artifact-url: + description: The URL to the uploaded artifact + value: ${{ steps.upload-artifact.outputs.artifact_url }} + runs: using: composite steps: @@ -100,6 +105,7 @@ runs: - name: Store Golangci-lint report artifact if: always() + id: upload-artifact uses: actions/upload-artifact@v4.4.3 with: # Use a unique suffix for each lint report artifact to avoid duplication errors diff --git a/.github/integration-in-memory-tests.yml b/.github/integration-in-memory-tests.yml index 01bb90044f4..a23ec5432ee 100644 --- a/.github/integration-in-memory-tests.yml +++ b/.github/integration-in-memory-tests.yml @@ -54,6 +54,14 @@ runner-test-matrix: triggers: - PR Integration CCIP Tests test_cmd: cd integration-tests/smoke/ccip && go test ccip_batching_test.go -timeout 12m -test.parallel=2 -count=1 -json + + - id: smoke/ccip/ccip_add_chain_test.go:* + path: integration-tests/smoke/ccip/ccip_add_chain_test.go + test_env_type: in-memory + runs_on: ubuntu-latest + triggers: + - PR Integration CCIP Tests + test_cmd: cd integration-tests/smoke/ccip && go test ccip_add_chain_test.go -timeout 15m -test.parallel=1 -count=1 -json - id: smoke/ccip/ccip_reader_test.go:* path: integration-tests/smoke/ccip/ccip_reader_test.go diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 5eafa0a6721..b593bfa218f 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -112,9 +112,17 @@ jobs: - name: Resolve affected files to affected modules id: resolved-modules shell: bash + env: + GH_EVENT_NAME: ${{ github.event_name }} run: | - # Ensure the step uses `with.list-files: json` to get the list of files in JSON format - bash ./.github/scripts/map-affected-files-to-modules.sh '${{ steps.match-every.outputs.all_files }}' + # if scheduled, run for all modules. Otherwise, run for only affected modules. + if [[ "$GH_EVENT_NAME" == "schedule" ]]; then + json_array=$(find . -name 'go.mod' -exec dirname {} \; | sed 's|^./||' | uniq | jq -R -s -c 'split("\n") | map(select(length > 0))') + echo "module_names=$json_array" >> "$GITHUB_OUTPUT" + else + # Ensure the step uses `with.list-files: json` to get the list of files in JSON format + bash ./.github/scripts/map-affected-files-to-modules.sh '${{ steps.match-every.outputs.all_files }}' + fi golangci: name: GolangCI Lint @@ -138,6 +146,7 @@ jobs: with: persist-credentials: false - name: Golang Lint (${{ matrix.modules }}) + id: golang-lint uses: ./.github/actions/golangci-lint with: go-directory: ${{ matrix.modules }} @@ -148,7 +157,10 @@ jobs: SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} with: channel-id: "#team-core" - slack-message: "golangci-lint failed: \n${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}" + slack-message: | + "golangci-lint failed (${{ matrix.modules }}) + - Run: ${{ format('https://github.com/{0}/actions/runs/{1}', github.repository, github.run_id) }}" + - Report: ${{ steps.golang-lint.outputs.golang-report-artifact-url }}" # Fails if any golangci-lint matrix jobs fails and silently succeeds otherwise # Consolidates golangci-lint matrix job results under one required `lint` check @@ -255,19 +267,7 @@ jobs: - name: Install LOOP Plugins if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} - run: | - pushd $(go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-feeds) - go install ./cmd/chainlink-feeds - popd - pushd $(go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-data-streams) - go install ./mercury/cmd/chainlink-mercury - popd - pushd $(go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-solana) - go install ./pkg/solana/cmd/chainlink-solana - popd - pushd $(go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-starknet/relayer) - go install ./pkg/chainlink/cmd/chainlink-starknet - popd + run: make install-plugins - name: Increase Timeouts for Fuzz/Race # Increase timeouts for scheduled runs only diff --git a/.mockery.yaml b/.mockery.yaml index b7dbb8a1e85..88dcf307d20 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -81,6 +81,12 @@ packages: BalanceMonitor: config: dir: "{{ .InterfaceDir }}/../mocks" + github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm: + interfaces: + Client: + TxStore: + AttemptBuilder: + Keystore: github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr: interfaces: ChainConfig: diff --git a/GNUmakefile b/GNUmakefile index f877f01decb..d86b791bbee 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -65,6 +65,17 @@ install-medianpoc: ## Build & install the chainlink-medianpoc binary. install-ocr3-capability: ## Build & install the chainlink-ocr3-capability binary. go install $(GOFLAGS) ./plugins/cmd/chainlink-ocr3-capability +.PHONY: install-plugins +install-plugins: ## Build & install LOOPP binaries for products and chains. + cd $(shell go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-feeds) && \ + go install ./cmd/chainlink-feeds + cd $(shell go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-data-streams) && \ + go install ./mercury/cmd/chainlink-mercury + cd $(shell go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-solana) && \ + go install ./pkg/solana/cmd/chainlink-solana + cd $(shell go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-starknet/relayer) && \ + go install ./pkg/chainlink/cmd/chainlink-starknet + .PHONY: docker ## Build the chainlink docker image docker: docker buildx build \ diff --git a/contracts/.changeset/clean-horses-cheat.md b/contracts/.changeset/clean-horses-cheat.md new file mode 100644 index 00000000000..09cce822451 --- /dev/null +++ b/contracts/.changeset/clean-horses-cheat.md @@ -0,0 +1,10 @@ +--- +'@chainlink/contracts': minor +--- + +Update FeeQuoter to support Solana chain families #feature + + +PR issue: CCIP-4687 + +Solidity Review issue: CCIP-3966 \ No newline at end of file diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index f1eefe78fa8..a6cb9fe68fc 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -9,7 +9,7 @@ BurnToAddressMintTokenPool_releaseOrMint:test_releaseOrMint() (gas: 126048) BurnToAddressMintTokenPool_setOutstandingokens:test_setOutstandingTokens() (gas: 37793) BurnWithFromMintTokenPool_lockOrBurn:test_PoolBurn() (gas: 239012) BurnWithFromMintTokenPool_lockOrBurn:test_Setup() (gas: 24169) -CCIPClientExample_sanity:test_ImmutableExamples() (gas: 2073613) +CCIPClientExample_sanity:test_ImmutableExamples() (gas: 2078596) CCIPHome__validateConfig:test__validateConfig() (gas: 300016) CCIPHome__validateConfig:test__validateConfigLessTransmittersThanSigners() (gas: 332965) CCIPHome__validateConfig:test__validateConfigSmallerFChain() (gas: 459322) @@ -25,9 +25,9 @@ CCIPHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive_mu CCIPHome_revokeCandidate:test_revokeCandidate() (gas: 30647) CCIPHome_setCandidate:test_setCandidate() (gas: 1365392) CCIPHome_supportsInterface:test_supportsInterface() (gas: 9885) -DefensiveExampleTest:test_HappyPath() (gas: 200517) +DefensiveExampleTest:test_HappyPath() (gas: 200535) DefensiveExampleTest:test_Recovery() (gas: 424996) -E2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1490723) +E2E:test_E2E_3MessagesMMultiOffRampSuccess_gas() (gas: 1494287) ERC165CheckerReverting_supportsInterfaceReverting:test__supportsInterfaceReverting() (gas: 10445) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_fallbackToWethTransfer() (gas: 96964) EtherSenderReceiverTest_ccipReceive:test_ccipReceive_happyPath() (gas: 49797) @@ -62,7 +62,7 @@ FactoryBurnMintERC20_increaseApproval:test_IncreaseApproval() (gas: 44421) FactoryBurnMintERC20_mint:test_BasicMint() (gas: 149826) FactoryBurnMintERC20_supportsInterface:test_SupportsInterface() (gas: 11539) FactoryBurnMintERC20_transfer:test_Transfer() (gas: 42505) -FeeQuoter_applyDestChainConfigUpdates:test_applyDestChainConfigUpdates() (gas: 149177) +FeeQuoter_applyDestChainConfigUpdates:test_applyDestChainConfigUpdates() (gas: 149063) FeeQuoter_applyDestChainConfigUpdates:test_applyDestChainConfigUpdatesZeroInput() (gas: 12493) FeeQuoter_applyFeeTokensUpdates:test_ApplyFeeTokensUpdates() (gas: 162480) FeeQuoter_applyPremiumMultiplierWeiPerEthUpdates:test_applyPremiumMultiplierWeiPerEthUpdatesMultipleTokens() (gas: 54881) @@ -70,31 +70,32 @@ FeeQuoter_applyPremiumMultiplierWeiPerEthUpdates:test_applyPremiumMultiplierWeiP FeeQuoter_applyPremiumMultiplierWeiPerEthUpdates:test_applyPremiumMultiplierWeiPerEthUpdatesZeroInput() (gas: 12468) FeeQuoter_applyTokenTransferFeeConfigUpdates:test_ApplyTokenTransferFeeConfig() (gas: 88604) FeeQuoter_applyTokenTransferFeeConfigUpdates:test_ApplyTokenTransferFeeZeroInput() (gas: 13196) -FeeQuoter_constructor:test_Setup() (gas: 5034079) +FeeQuoter_constructor:test_Setup() (gas: 5429233) FeeQuoter_convertTokenAmount:test_ConvertTokenAmount() (gas: 68417) -FeeQuoter_getDataAvailabilityCost:test_EmptyMessageCalculatesDataAvailabilityCost() (gas: 98963) -FeeQuoter_getDataAvailabilityCost:test_SimpleMessageCalculatesDataAvailabilityCost() (gas: 21527) +FeeQuoter_getDataAvailabilityCost:test_EmptyMessageCalculatesDataAvailabilityCost() (gas: 98884) +FeeQuoter_getDataAvailabilityCost:test_SimpleMessageCalculatesDataAvailabilityCost() (gas: 21505) FeeQuoter_getDataAvailabilityCost:test_SimpleMessageCalculatesDataAvailabilityCostUnsupportedDestChainSelector() (gas: 14904) FeeQuoter_getTokenAndGasPrices:test_GetFeeTokenAndGasPrices() (gas: 73123) -FeeQuoter_getTokenAndGasPrices:test_StalenessCheckDisabled() (gas: 113611) -FeeQuoter_getTokenAndGasPrices:test_ZeroGasPrice() (gas: 110698) +FeeQuoter_getTokenAndGasPrices:test_StalenessCheckDisabled() (gas: 113576) +FeeQuoter_getTokenAndGasPrices:test_ZeroGasPrice() (gas: 110663) FeeQuoter_getTokenPrice:test_GetTokenPriceFromFeed() (gas: 68158) FeeQuoter_getTokenPrice:test_GetTokenPrice_LocalMoreRecent() (gas: 33546) FeeQuoter_getTokenPrices:test_GetTokenPrices() (gas: 78534) -FeeQuoter_getTokenTransferCost:test_CustomTokenBpsFee() (gas: 34575) -FeeQuoter_getTokenTransferCost:test_FeeTokenBpsFee() (gas: 32354) -FeeQuoter_getTokenTransferCost:test_LargeTokenTransferChargesMaxFeeAndGas() (gas: 25402) -FeeQuoter_getTokenTransferCost:test_MixedTokenTransferFee() (gas: 91665) -FeeQuoter_getTokenTransferCost:test_NoTokenTransferChargesZeroFee() (gas: 17863) -FeeQuoter_getTokenTransferCost:test_SmallTokenTransferChargesMinFeeAndGas() (gas: 25231) -FeeQuoter_getTokenTransferCost:test_ZeroAmountTokenTransferChargesMinFeeAndGas() (gas: 25232) -FeeQuoter_getTokenTransferCost:test_ZeroFeeConfigChargesMinFee() (gas: 37790) -FeeQuoter_getTokenTransferCost:test_getTokenTransferCost_selfServeUsesDefaults() (gas: 26926) -FeeQuoter_getValidatedFee:test_EmptyMessage() (gas: 84794) -FeeQuoter_getValidatedFee:test_HighGasMessage() (gas: 242730) -FeeQuoter_getValidatedFee:test_MessageWithDataAndTokenTransfer() (gas: 143322) -FeeQuoter_getValidatedFee:test_SingleTokenMessage() (gas: 114915) -FeeQuoter_getValidatedFee:test_ZeroDataAvailabilityMultiplier() (gas: 66086) +FeeQuoter_getTokenTransferCost:test_CustomTokenBpsFee() (gas: 34616) +FeeQuoter_getTokenTransferCost:test_FeeTokenBpsFee() (gas: 32395) +FeeQuoter_getTokenTransferCost:test_LargeTokenTransferChargesMaxFeeAndGas() (gas: 25465) +FeeQuoter_getTokenTransferCost:test_MixedTokenTransferFee() (gas: 91728) +FeeQuoter_getTokenTransferCost:test_NoTokenTransferChargesZeroFee() (gas: 17904) +FeeQuoter_getTokenTransferCost:test_SmallTokenTransferChargesMinFeeAndGas() (gas: 25272) +FeeQuoter_getTokenTransferCost:test_ZeroAmountTokenTransferChargesMinFeeAndGas() (gas: 25295) +FeeQuoter_getTokenTransferCost:test_ZeroFeeConfigChargesMinFee() (gas: 37853) +FeeQuoter_getTokenTransferCost:test_getTokenTransferCost_selfServeUsesDefaults() (gas: 26989) +FeeQuoter_getValidatedFee:test_EmptyMessage() (gas: 85158) +FeeQuoter_getValidatedFee:test_HighGasMessage() (gas: 243094) +FeeQuoter_getValidatedFee:test_MessageWithDataAndTokenTransfer() (gas: 143736) +FeeQuoter_getValidatedFee:test_SingleTokenMessage() (gas: 115240) +FeeQuoter_getValidatedFee:test_SolChainFamilySelector() (gas: 60999) +FeeQuoter_getValidatedFee:test_ZeroDataAvailabilityMultiplier() (gas: 66233) FeeQuoter_getValidatedTokenPrice:test_GetValidatedTokenPrice() (gas: 58905) FeeQuoter_getValidatedTokenPrice:test_GetValidatedTokenPriceFromFeed() (gas: 65115) FeeQuoter_getValidatedTokenPrice:test_GetValidatedTokenPriceFromFeedErc20Above18Decimals() (gas: 1897724) @@ -108,27 +109,35 @@ FeeQuoter_getValidatedTokenPrice:test_StaleFeeToken() (gas: 61854) FeeQuoter_onReport:test_OnReport_SkipPriceUpdateWhenStaleUpdateReceived() (gas: 52565) FeeQuoter_onReport:test_onReport() (gas: 88942) FeeQuoter_onReport:test_onReport_withKeystoneForwarderContract() (gas: 122570) -FeeQuoter_parseEVMExtraArgsFromBytes:test_EVMExtraArgsDefault() (gas: 17113) -FeeQuoter_parseEVMExtraArgsFromBytes:test_EVMExtraArgsV1() (gas: 16202) -FeeQuoter_parseEVMExtraArgsFromBytes:test_EVMExtraArgsV2() (gas: 16306) -FeeQuoter_processMessageArgs:test_processMessageArgs_WitEVMExtraArgsV2() (gas: 27978) -FeeQuoter_processMessageArgs:test_processMessageArgs_WithConvertedTokenAmount() (gas: 32012) -FeeQuoter_processMessageArgs:test_processMessageArgs_WithCorrectPoolReturnData() (gas: 76591) -FeeQuoter_processMessageArgs:test_processMessageArgs_WithEVMExtraArgsV1() (gas: 27609) -FeeQuoter_processMessageArgs:test_processMessageArgs_WithEmptyEVMExtraArgs() (gas: 25468) -FeeQuoter_processMessageArgs:test_processMessageArgs_WithLinkTokenAmount() (gas: 21652) +FeeQuoter_parseSVMExtraArgsFromBytes:test_SVMExtraArgsV1() (gas: 23233) +FeeQuoter_parseSVMExtraArgsFromBytes:test_SVMExtraArgsV1TagSelector() (gas: 3157) +FeeQuoter_processChainFamilySelector:test_processChainFamilySelectorEVM() (gas: 19457) +FeeQuoter_processChainFamilySelector:test_processChainFamilySelectorSVM_NoTokenTransfer() (gas: 22644) +FeeQuoter_processChainFamilySelector:test_processChainFamilySelectorSVM_WithTokenTransfer() (gas: 22633) +FeeQuoter_processMessageArgs:test_processMessageArgs_WitEVMExtraArgsV2() (gas: 29003) +FeeQuoter_processMessageArgs:test_processMessageArgs_WithConvertedTokenAmount() (gas: 33038) +FeeQuoter_processMessageArgs:test_processMessageArgs_WithCorrectPoolReturnData() (gas: 77567) +FeeQuoter_processMessageArgs:test_processMessageArgs_WithEVMExtraArgsV1() (gas: 28634) +FeeQuoter_processMessageArgs:test_processMessageArgs_WithEmptyEVMExtraArgs() (gas: 26494) +FeeQuoter_processMessageArgs:test_processMessageArgs_WithLinkTokenAmount() (gas: 22700) +FeeQuoter_processMessageArgs:test_processMessageArgs_WithSVMExtraArgsV1() (gas: 63415) +FeeQuoter_resolveGasLimitForDestination:test_EVMExtraArgsDefault() (gas: 17113) +FeeQuoter_resolveGasLimitForDestination:test_EVMExtraArgsV1() (gas: 16180) +FeeQuoter_resolveGasLimitForDestination:test_EVMExtraArgsV1TagSelector() (gas: 3169) +FeeQuoter_resolveGasLimitForDestination:test_EVMExtraArgsV2() (gas: 16306) +FeeQuoter_resolveGasLimitForDestination:test_EVMExtraArgsV2TagSelector() (gas: 3168) FeeQuoter_supportsInterface:test_SupportsInterface() (gas: 13264) -FeeQuoter_updatePrices:test_OnlyGasPrice() (gas: 23912) +FeeQuoter_updatePrices:test_OnlyGasPrice() (gas: 24001) FeeQuoter_updatePrices:test_OnlyTokenPrice() (gas: 28739) -FeeQuoter_updatePrices:test_UpdatableByAuthorizedCaller() (gas: 74768) -FeeQuoter_updatePrices:test_UpdateMultiplePrices() (gas: 145958) +FeeQuoter_updatePrices:test_UpdatableByAuthorizedCaller() (gas: 74733) +FeeQuoter_updatePrices:test_UpdateMultiplePrices() (gas: 146225) FeeQuoter_updateTokenPriceFeeds:test_FeedNotUpdated() (gas: 52495) FeeQuoter_updateTokenPriceFeeds:test_FeedUnset() (gas: 66418) FeeQuoter_updateTokenPriceFeeds:test_MultipleFeedUpdate() (gas: 93559) FeeQuoter_updateTokenPriceFeeds:test_SingleFeedUpdate() (gas: 53171) FeeQuoter_updateTokenPriceFeeds:test_ZeroFeeds() (gas: 12471) -FeeQuoter_validateDestFamilyAddress:test_ValidEVMAddress() (gas: 6767) -FeeQuoter_validateDestFamilyAddress:test_ValidNonEVMAddress() (gas: 6492) +FeeQuoter_validateDestFamilyAddress:test_ValidEVMAddress() (gas: 6796) +FeeQuoter_validateDestFamilyAddress:test_ValidSVMAddress() (gas: 6657) HybridLockReleaseUSDCTokenPool_lockOrBurn:test_PrimaryMechanism() (gas: 130339) HybridLockReleaseUSDCTokenPool_lockOrBurn:test_onLockReleaseMechanism() (gas: 140169) HybridLockReleaseUSDCTokenPool_lockOrBurn:test_onLockReleaseMechanism_thenSwitchToPrimary() (gas: 202967) @@ -190,10 +199,10 @@ MultiOCR3Base_setOCR3Configs:test_UpdateConfigSigners() (gas: 861909) MultiOCR3Base_setOCR3Configs:test_UpdateConfigTransmittersWithoutSigners() (gas: 476109) MultiOCR3Base_transmit:test_TransmitSigners_gas() (gas: 33559) MultiOCR3Base_transmit:test_TransmitWithoutSignatureVerification_gas() (gas: 18638) -NonceManager_applyPreviousRampsUpdates:test_MultipleRampsUpdates() (gas: 123595) +NonceManager_applyPreviousRampsUpdates:test_MultipleRampsUpdates() (gas: 123617) NonceManager_applyPreviousRampsUpdates:test_PreviousRampAlreadySet_overrideAllowed() (gas: 45935) NonceManager_applyPreviousRampsUpdates:test_SingleRampUpdate() (gas: 66937) -NonceManager_applyPreviousRampsUpdates:test_ZeroInput() (gas: 12145) +NonceManager_applyPreviousRampsUpdates:test_ZeroInput() (gas: 12123) NonceManager_getInboundNonce:test_getInboundNonce_NoPrevOffRampForChain() (gas: 178906) NonceManager_getInboundNonce:test_getInboundNonce_Upgraded() (gas: 146095) NonceManager_getInboundNonce:test_getInboundNonce_UpgradedNonceNewSenderStartsAtZero() (gas: 182376) @@ -204,10 +213,10 @@ NonceManager_getIncrementedOutboundNonce:test_getIncrementedOutboundNonce() (gas NonceManager_getIncrementedOutboundNonce:test_incrementInboundNonce() (gas: 38746) NonceManager_getIncrementedOutboundNonce:test_incrementInboundNonce_SkippedIncorrectNonce() (gas: 23739) NonceManager_getIncrementedOutboundNonce:test_incrementNoncesInboundAndOutbound() (gas: 71886) -NonceManager_getOutboundNonce:test_getOutboundNonce_Upgrade() (gas: 104563) -NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeNonceNewSenderStartsAtZero() (gas: 165406) -NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeNonceStartsAtV1Nonce() (gas: 194435) -NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeSenderNoncesReadsPreviousRamp() (gas: 142134) +NonceManager_getOutboundNonce:test_getOutboundNonce_Upgrade() (gas: 105588) +NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeNonceNewSenderStartsAtZero() (gas: 167456) +NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeNonceStartsAtV1Nonce() (gas: 197510) +NonceManager_getOutboundNonce:test_getOutboundNonce_UpgradeSenderNoncesReadsPreviousRamp() (gas: 145209) OffRamp_applySourceChainConfigUpdates:test_AddMultipleChains() (gas: 626140) OffRamp_applySourceChainConfigUpdates:test_AddNewChain() (gas: 166441) OffRamp_applySourceChainConfigUpdates:test_ApplyZeroUpdates() (gas: 16671) @@ -276,33 +285,33 @@ OffRamp_trialExecute:test_trialExecute_RevertsWhen_NoEnoughGasForCallSigAndSende OffRamp_trialExecute:test_trialExecute_RevertsWhen_NoGasForCallExactCheckAndSenderIsGasEstimator() (gas: 29539) OffRamp_trialExecute:test_trialExecute_TokenHandlingErrorIsCaught() (gas: 131932) OffRamp_trialExecute:test_trialExecute_TokenPoolIsNotAContract() (gas: 281327) -OnRampTokenPoolReentrancy:test_OnRampTokenPoolReentrancy() (gas: 244845) -OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates() (gas: 325979) +OnRampTokenPoolReentrancy:test_OnRampTokenPoolReentrancy() (gas: 245406) +OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates() (gas: 325996) OnRamp_applyAllowlistUpdates:test_applyAllowlistUpdates_InvalidAllowListRequestDisabledAllowListWithAdds() (gas: 17190) OnRamp_applyDestChainConfigUpdates:test_ApplyDestChainConfigUpdates() (gas: 65874) -OnRamp_constructor:test_Constructor() (gas: 2672129) -OnRamp_forwardFromRouter:test_ForwardFromRouter() (gas: 144671) -OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2() (gas: 145505) -OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue() (gas: 114684) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessCustomExtraArgs() (gas: 145069) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessEmptyExtraArgs() (gas: 143346) -OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessLegacyExtraArgs() (gas: 145310) -OnRamp_forwardFromRouter:test_ForwardFromRouter_ConfigurableSourceRouter() (gas: 142650) -OnRamp_forwardFromRouter:test_ShouldIncrementNonceOnlyOnOrdered() (gas: 184400) -OnRamp_forwardFromRouter:test_ShouldIncrementSeqNumAndNonce() (gas: 210755) -OnRamp_forwardFromRouter:test_ShouldStoreLinkFees() (gas: 146316) -OnRamp_forwardFromRouter:test_forwardFromRouter_WithInterception() (gas: 274013) -OnRamp_getFee:test_EmptyMessage() (gas: 99565) -OnRamp_getFee:test_GetFeeOfZeroForTokenMessage() (gas: 89015) -OnRamp_getFee:test_SingleTokenMessage() (gas: 114859) -OnRamp_getTokenPool:test_GetTokenPool() (gas: 35382) +OnRamp_constructor:test_Constructor() (gas: 2672063) +OnRamp_forwardFromRouter:test_ForwardFromRouter() (gas: 145718) +OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2() (gas: 146530) +OnRamp_forwardFromRouter:test_ForwardFromRouterExtraArgsV2AllowOutOfOrderTrue() (gas: 115709) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessCustomExtraArgs() (gas: 146116) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessEmptyExtraArgs() (gas: 144372) +OnRamp_forwardFromRouter:test_ForwardFromRouterSuccessLegacyExtraArgs() (gas: 146357) +OnRamp_forwardFromRouter:test_ForwardFromRouter_ConfigurableSourceRouter() (gas: 143697) +OnRamp_forwardFromRouter:test_ShouldIncrementNonceOnlyOnOrdered() (gas: 187475) +OnRamp_forwardFromRouter:test_ShouldIncrementSeqNumAndNonce() (gas: 213852) +OnRamp_forwardFromRouter:test_ShouldStoreLinkFees() (gas: 147363) +OnRamp_forwardFromRouter:test_forwardFromRouter_WithInterception() (gas: 275037) +OnRamp_getFee:test_EmptyMessage() (gas: 100389) +OnRamp_getFee:test_GetFeeOfZeroForTokenMessage() (gas: 89392) +OnRamp_getFee:test_SingleTokenMessage() (gas: 115707) +OnRamp_getTokenPool:test_GetTokenPool() (gas: 35404) OnRamp_setDynamicConfig:test_setDynamicConfig() (gas: 56650) OnRamp_withdrawFeeTokens:test_WithdrawFeeTokens() (gas: 125835) -PingPong_ccipReceive:test_CcipReceive() (gas: 165996) -PingPong_setOutOfOrderExecution:test_OutOfOrderExecution() (gas: 20350) -PingPong_setPaused:test_Pausing() (gas: 17738) -PingPong_startPingPong:test_StartPingPong_With_OOO() (gas: 145147) -PingPong_startPingPong:test_StartPingPong_With_Sequenced_Ordered() (gas: 170800) +PingPong_ccipReceive:test_CcipReceive() (gas: 167206) +PingPong_setOutOfOrderExecution:test_OutOfOrderExecution() (gas: 20284) +PingPong_setPaused:test_Pausing() (gas: 17760) +PingPong_startPingPong:test_StartPingPong_With_OOO() (gas: 146357) +PingPong_startPingPong:test_StartPingPong_With_Sequenced_Ordered() (gas: 171988) RMNHome_getConfigDigests:test_getConfigDigests() (gas: 1081176) RMNHome_promoteCandidateAndRevokeActive:test_promoteCandidateAndRevokeActive() (gas: 1086556) RMNHome_revokeCandidate:test_revokeCandidate() (gas: 28085) @@ -332,21 +341,21 @@ RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin:test_registerAdminViaGetC RegistryModuleOwnerCustom_registerAdminViaOwner:test_registerAdminViaOwner() (gas: 129941) Router_applyRampUpdates:test_applyRampUpdates_OffRampUpdatesWithRouting() (gas: 10413055) Router_applyRampUpdates:test_applyRampUpdates_OnRampDisable() (gas: 56445) -Router_ccipSend:test_CCIPSendLinkFeeNoTokenSuccess_gas() (gas: 124610) -Router_ccipSend:test_CCIPSendLinkFeeOneTokenSuccess_gas() (gas: 212051) +Router_ccipSend:test_CCIPSendLinkFeeNoTokenSuccess_gas() (gas: 125820) +Router_ccipSend:test_CCIPSendLinkFeeOneTokenSuccess_gas() (gas: 213241) Router_ccipSend:test_InvalidMsgValue() (gas: 27856) -Router_ccipSend:test_NativeFeeToken() (gas: 185287) -Router_ccipSend:test_NativeFeeTokenInsufficientValue() (gas: 62598) -Router_ccipSend:test_NativeFeeTokenOverpay() (gas: 186704) -Router_ccipSend:test_NativeFeeTokenZeroValue() (gas: 54690) -Router_ccipSend:test_NonLinkFeeToken() (gas: 219712) -Router_ccipSend:test_WrappedNativeFeeToken() (gas: 187526) -Router_ccipSend:test_ccipSend_nativeFeeNoTokenSuccess_gas() (gas: 133767) -Router_ccipSend:test_ccipSend_nativeFeeOneTokenSuccess_gas() (gas: 221252) -Router_constructor:test_Constructor() (gas: 13148) +Router_ccipSend:test_NativeFeeToken() (gas: 186661) +Router_ccipSend:test_NativeFeeTokenInsufficientValue() (gas: 62761) +Router_ccipSend:test_NativeFeeTokenOverpay() (gas: 188056) +Router_ccipSend:test_NativeFeeTokenZeroValue() (gas: 54853) +Router_ccipSend:test_NonLinkFeeToken() (gas: 220901) +Router_ccipSend:test_WrappedNativeFeeToken() (gas: 188878) +Router_ccipSend:test_ccipSend_nativeFeeNoTokenSuccess_gas() (gas: 134978) +Router_ccipSend:test_ccipSend_nativeFeeOneTokenSuccess_gas() (gas: 222442) +Router_constructor:test_Constructor() (gas: 13170) Router_getArmProxy:test_getArmProxy() (gas: 10573) -Router_getFee:test_GetFeeSupportedChain() (gas: 52161) -Router_recoverTokens:test_RecoverTokens() (gas: 52668) +Router_getFee:test_GetFeeSupportedChain() (gas: 52432) +Router_recoverTokens:test_RecoverTokens() (gas: 52686) Router_routeMessage:test_routeMessage_AutoExec() (gas: 38071) Router_routeMessage:test_routeMessage_ExecutionEvent() (gas: 153593) Router_routeMessage:test_routeMessage_ManualExec() (gas: 31120) diff --git a/contracts/scripts/native_solc_compile_all_ccip b/contracts/scripts/native_solc_compile_all_ccip index 8c3168c4fc3..a62a25ddbb0 100755 --- a/contracts/scripts/native_solc_compile_all_ccip +++ b/contracts/scripts/native_solc_compile_all_ccip @@ -9,6 +9,7 @@ echo " └─────────────────────── # The offRamp uses a specific lower optimization runs value. All other contracts use the default value # as specified in the foundry.toml. OPTIMIZE_RUNS_OFFRAMP=800 +OPTIMIZE_RUNS_FEE_QUOTER=10000 PROJECT="ccip" FOUNDRY_PROJECT_SUFFIX="-compile" @@ -37,7 +38,10 @@ function getOptimizations() { case $1 in "OffRamp") - optimize_runs_override="--optimizer-runs "$OPTIMIZE_RUNS_OFFRAMP + optimize_runs_override="--optimizer-runs $OPTIMIZE_RUNS_OFFRAMP" + ;; + "FeeQuoter") + optimize_runs_override="--optimizer-runs $OPTIMIZE_RUNS_FEE_QUOTER" ;; esac diff --git a/contracts/scripts/native_solc_compile_all_feeds b/contracts/scripts/native_solc_compile_all_feeds index c6b80958156..9e0a58b4364 100755 --- a/contracts/scripts/native_solc_compile_all_feeds +++ b/contracts/scripts/native_solc_compile_all_feeds @@ -30,5 +30,6 @@ compileContract () { } # Aggregators +compileContract shared/interfaces/AggregatorV2V3Interface.sol compileContract operatorforwarder/Chainlink.sol compileContract operatorforwarder/ChainlinkClient.sol diff --git a/contracts/src/v0.8/ccip/FeeQuoter.sol b/contracts/src/v0.8/ccip/FeeQuoter.sol index 40972df1c0b..590233c6230 100644 --- a/contracts/src/v0.8/ccip/FeeQuoter.sol +++ b/contracts/src/v0.8/ccip/FeeQuoter.sol @@ -34,9 +34,11 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, error DataFeedValueOutOfUint224Range(); error InvalidDestBytesOverhead(address token, uint32 destBytesOverhead); error MessageGasLimitTooHigh(); + error MessageComputeUnitLimitTooHigh(); error DestinationChainNotEnabled(uint64 destChainSelector); error ExtraArgOutOfOrderExecutionMustBeTrue(); error InvalidExtraArgsTag(); + error InvalidExtraArgsData(); error SourceTokenDataTooLarge(address token); error InvalidDestChainConfig(uint64 destChainSelector); error MessageFeeTooHigh(uint256 msgFeeJuels, uint256 maxFeeJuelsPerMsg); @@ -44,6 +46,8 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, error MessageTooLarge(uint256 maxSize, uint256 actualSize); error UnsupportedNumberOfTokens(uint256 numberOfTokens, uint256 maxNumberOfTokensPerMsg); error InvalidFeeRange(uint256 minFeeUSDCents, uint256 maxFeeUSDCents); + error InvalidChainFamilySelector(bytes4 chainFamilySelector); + error InvalidTokenReceiver(); event FeeTokenAdded(address indexed feeToken); event FeeTokenRemoved(address indexed feeToken); @@ -551,11 +555,12 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, Client.EVM2AnyMessage calldata message ) external view returns (uint256 feeTokenAmount) { DestChainConfig memory destChainConfig = s_destChainConfigs[destChainSelector]; - if (!destChainConfig.isEnabled) revert DestinationChainNotEnabled(destChainSelector); if (!s_feeTokens.contains(message.feeToken)) revert FeeTokenNotSupported(message.feeToken); uint256 numberOfTokens = message.tokenAmounts.length; - _validateMessage(destChainConfig, message.data.length, numberOfTokens, message.receiver); + uint256 gasLimit = _resolveGasLimitForDestination(message.extraArgs, destChainConfig); + + _validateMessage(destChainConfig, message.data.length, numberOfTokens, gasLimit, message.receiver); // The below call asserts that feeToken is a supported token. uint224 feeTokenPrice = _getValidatedTokenPrice(message.feeToken); @@ -622,13 +627,7 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, // We add the destination chain CCIP overhead (commit, exec), the token transfer gas, the calldata cost and the msg // gas limit to get the total gas the tx costs to execute on the destination chain. - uint256 totalDestChainGas = destChainConfig.destGasOverhead + tokenTransferGas + destCallDataCost - + _parseEVMExtraArgsFromBytes( - message.extraArgs, - destChainConfig.defaultTxGasLimit, - destChainConfig.maxPerMsgGasLimit, - destChainConfig.enforceOutOfOrder - ).gasLimit; + uint256 totalDestChainGas = destChainConfig.destGasOverhead + tokenTransferGas + destCallDataCost + gasLimit; // Total USD fee is in 36 decimals, feeTokenPrice is in 18 decimals USD for 1e18 smallest token denominations. // The result is the fee in the feeTokens smallest denominations (e.g. wei for ETH). @@ -877,10 +876,66 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, /// @param chainFamilySelector Tag to identify the target family. /// @param destAddress Dest address to validate. /// @dev precondition - assumes the family tag is correct and validated. - function _validateDestFamilyAddress(bytes4 chainFamilySelector, bytes memory destAddress) internal pure { + function _validateDestFamilyAddress( + bytes4 chainFamilySelector, + bytes memory destAddress, + uint256 gasLimit + ) internal pure { if (chainFamilySelector == Internal.CHAIN_FAMILY_SELECTOR_EVM) { - Internal._validateEVMAddress(destAddress); + return Internal._validateEVMAddress(destAddress); } + if (chainFamilySelector == Internal.CHAIN_FAMILY_SELECTOR_SVM) { + return Internal._validateSVMAddress(destAddress, gasLimit > 0); + } + revert InvalidChainFamilySelector(chainFamilySelector); + } + + function _resolveGasLimitForDestination( + bytes calldata extraArgs, + DestChainConfig memory destChainConfig + ) internal pure returns (uint256) { + if (destChainConfig.chainFamilySelector == Internal.CHAIN_FAMILY_SELECTOR_EVM) { + return _parseEVMExtraArgsFromBytes( + extraArgs, + destChainConfig.defaultTxGasLimit, + destChainConfig.maxPerMsgGasLimit, + destChainConfig.enforceOutOfOrder + ).gasLimit; + } + if (destChainConfig.chainFamilySelector == Internal.CHAIN_FAMILY_SELECTOR_SVM) { + return _parseSVMExtraArgsFromBytes( + extraArgs, destChainConfig.maxPerMsgGasLimit, destChainConfig.enforceOutOfOrder + ).computeUnits; + } + revert InvalidChainFamilySelector(destChainConfig.chainFamilySelector); + } + + /// @notice Parse and validate the SVM specific Extra Args Bytes. + function _parseSVMExtraArgsFromBytes( + bytes calldata extraArgs, + uint256 maxPerMsgGasLimit, + bool enforcedOutOfOrder + ) internal pure returns (Client.SVMExtraArgsV1 memory svmExtraArgs) { + if (extraArgs.length == 0) { + revert InvalidExtraArgsData(); + } + + bytes4 tag = bytes4(extraArgs[:4]); + if (tag != Client.SVM_EXTRA_EXTRA_ARGS_V1_TAG) { + revert InvalidExtraArgsTag(); + } + + svmExtraArgs = abi.decode(extraArgs[4:], (Client.SVMExtraArgsV1)); + + if (enforcedOutOfOrder && !svmExtraArgs.allowOutOfOrderExecution) { + revert ExtraArgOutOfOrderExecutionMustBeTrue(); + } + + if (svmExtraArgs.computeUnits > maxPerMsgGasLimit) { + revert MessageComputeUnitLimitTooHigh(); + } + + return svmExtraArgs; } /// @dev Convert the extra args bytes into a struct with validations against the dest chain config. @@ -929,7 +984,6 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, // Clients may still include it but it will be ignored. return Client.EVMExtraArgsV2({gasLimit: abi.decode(argsData, (uint256)), allowOutOfOrderExecution: false}); } - revert InvalidExtraArgsTag(); } @@ -943,6 +997,7 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, DestChainConfig memory destChainConfig, uint256 dataLength, uint256 numberOfTokens, + uint256 gasLimit, bytes memory receiver ) internal pure { // Check that payload is formed correctly. @@ -952,7 +1007,7 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, if (numberOfTokens > uint256(destChainConfig.maxNumberOfTokensPerMsg)) { revert UnsupportedNumberOfTokens(numberOfTokens, destChainConfig.maxNumberOfTokensPerMsg); } - _validateDestFamilyAddress(destChainConfig.chainFamilySelector, receiver); + _validateDestFamilyAddress(destChainConfig.chainFamilySelector, receiver, gasLimit); } /// @inheritdoc IFeeQuoter @@ -983,16 +1038,42 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, if (msgFeeJuels > i_maxFeeJuelsPerMsg) revert MessageFeeTooHigh(msgFeeJuels, i_maxFeeJuelsPerMsg); - uint64 defaultTxGasLimit = s_destChainConfigs[destChainSelector].defaultTxGasLimit; + (convertedExtraArgs, isOutOfOrderExecution) = + _processChainFamilySelector(destChainSelector, sourceTokenAmounts.length > 0, extraArgs); - // NOTE: Only EVM chains are supported for now, additional validation logic will be added when supporting other - // chain families to parse non-EVM args. - // Since the message is called after getFee, which will already validate the params, no validation is necessary. - Client.EVMExtraArgsV2 memory parsedExtraArgs = _parseUnvalidatedEVMExtraArgsFromBytes(extraArgs, defaultTxGasLimit); - isOutOfOrderExecution = parsedExtraArgs.allowOutOfOrderExecution; destExecDataPerToken = _processPoolReturnData(destChainSelector, onRampTokenTransfers, sourceTokenAmounts); - return (msgFeeJuels, isOutOfOrderExecution, Client._argsToBytes(parsedExtraArgs), destExecDataPerToken); + return (msgFeeJuels, isOutOfOrderExecution, convertedExtraArgs, destExecDataPerToken); + } + + /// @notice Parses the extra Args based on the chain family selector. Isolated into a separate function + /// as it was the only way to prevent a stack too deep error, and makes future chain family additions easier. + function _processChainFamilySelector( + uint64 destChainSelector, + bool isMessageWithTokenTransfer, + bytes calldata extraArgs + ) internal view returns (bytes memory, bool) { + DestChainConfig memory destChainConfig = s_destChainConfigs[destChainSelector]; + if (destChainConfig.chainFamilySelector == Internal.CHAIN_FAMILY_SELECTOR_EVM) { + // Since the message is called after getFee, which already validates the params, no validation is necessary. + Client.EVMExtraArgsV2 memory parsedExtraArgs = + _parseUnvalidatedEVMExtraArgsFromBytes(extraArgs, destChainConfig.defaultTxGasLimit); + + return (Client._argsToBytes(parsedExtraArgs), parsedExtraArgs.allowOutOfOrderExecution); + } + if (destChainConfig.chainFamilySelector == Internal.CHAIN_FAMILY_SELECTOR_SVM) { + bytes32 tokenReceiver = _parseSVMExtraArgsFromBytes( + extraArgs, destChainConfig.maxPerMsgGasLimit, destChainConfig.enforceOutOfOrder + ).tokenReceiver; + if (isMessageWithTokenTransfer && tokenReceiver == bytes32(0)) { + revert InvalidTokenReceiver(); + } + + // ExtraArgs are required on SVM, meaning the supplied extraArgs are either invalid and we would have reverted + // or we have valid extraArgs and we can return them without having to re-encode them. + return (extraArgs, true); + } + revert InvalidChainFamilySelector(destChainConfig.chainFamilySelector); } /// @notice Validates pool return data. @@ -1020,12 +1101,13 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, } } - _validateDestFamilyAddress(chainFamilySelector, onRampTokenTransfers[i].destTokenAddress); + // We pass '1' here so that SVM validation requires a non-zero token address. + // The 'gasLimit' parameter isn't actually used for gas in this context; it simply + // signals that the address must not be zero on SVM. + _validateDestFamilyAddress(chainFamilySelector, onRampTokenTransfers[i].destTokenAddress, 1); FeeQuoter.TokenTransferFeeConfig memory tokenTransferFeeConfig = s_tokenTransferFeeConfig[destChainSelector][sourceToken]; - // NOTE: Only EVM chains' gas model is supported for now, additional fee logic for non-EVM chains will - // be required in the future with parsing based on chain family selector. uint32 destGasAmount = tokenTransferFeeConfig.isEnabled ? tokenTransferFeeConfig.destGasOverhead : s_destChainConfigs[destChainSelector].defaultTokenDestGasOverhead; @@ -1067,12 +1149,9 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver, uint64 destChainSelector = destChainConfigArgs[i].destChainSelector; DestChainConfig memory destChainConfig = destChainConfigArg.destChainConfig; - // destChainSelector must be non-zero, defaultTxGasLimit must be set, and must be less than maxPerMsgGasLimit. - // Only EVM chains are supported for now, additional validation logic will be added when supporting other chain - // families + // destChainSelector must be non-zero, defaultTxGasLimit must be set, must be less than maxPerMsgGasLimit if ( destChainSelector == 0 || destChainConfig.defaultTxGasLimit == 0 - || destChainConfig.chainFamilySelector != Internal.CHAIN_FAMILY_SELECTOR_EVM || destChainConfig.defaultTxGasLimit > destChainConfig.maxPerMsgGasLimit ) { revert InvalidDestChainConfig(destChainSelector); diff --git a/contracts/src/v0.8/ccip/libraries/Client.sol b/contracts/src/v0.8/ccip/libraries/Client.sol index b1e1ca4a7c5..cde66e46cc0 100644 --- a/contracts/src/v0.8/ccip/libraries/Client.sol +++ b/contracts/src/v0.8/ccip/libraries/Client.sol @@ -42,6 +42,9 @@ library Client { // bytes4(keccak256("CCIP EVMExtraArgsV2")); bytes4 public constant EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10; + // bytes4(keccak256("CCIP SVMExtraArgsV1")); + bytes4 public constant SVM_EXTRA_EXTRA_ARGS_V1_TAG = 0x1f3b3aba; + /// @param gasLimit: gas limit for the callback on the destination chain. /// @param allowOutOfOrderExecution: if true, it indicates that the message can be executed in any order relative to /// other messages from the same sender. This value's default varies by chain. On some chains, a particular value is @@ -51,9 +54,23 @@ library Client { bool allowOutOfOrderExecution; } + struct SVMExtraArgsV1 { + uint32 computeUnits; + uint64 accountIsWritableBitmap; + bool allowOutOfOrderExecution; + bytes32 tokenReceiver; + bytes32[] accounts; + } + function _argsToBytes( EVMExtraArgsV2 memory extraArgs ) internal pure returns (bytes memory bts) { return abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs); } + + function _svmArgsToBytes( + SVMExtraArgsV1 memory extraArgs + ) internal pure returns (bytes memory bts) { + return abi.encodeWithSelector(SVM_EXTRA_EXTRA_ARGS_V1_TAG, extraArgs); + } } diff --git a/contracts/src/v0.8/ccip/libraries/Internal.sol b/contracts/src/v0.8/ccip/libraries/Internal.sol index 443ea22a107..435e26733a9 100644 --- a/contracts/src/v0.8/ccip/libraries/Internal.sol +++ b/contracts/src/v0.8/ccip/libraries/Internal.sol @@ -10,6 +10,7 @@ import {MerkleMultiProof} from "../libraries/MerkleMultiProof.sol"; /// expect to have migrated to a new version by then. library Internal { error InvalidEVMAddress(bytes encodedAddress); + error InvalidSVMAddress(bytes SVMAddress); /// @dev We limit return data to a selector plus 4 words. This is to avoid malicious contracts from returning /// large amounts of data and causing repeated out-of-gas scenarios. @@ -166,16 +167,23 @@ library Internal { /// @notice This methods provides validation for parsing abi encoded addresses by ensuring the address is within the /// EVM address space. If it isn't it will revert with an InvalidEVMAddress error, which we can catch and handle /// more gracefully than a revert from abi.decode. - /// @return The address if it is valid, the function will revert otherwise. function _validateEVMAddress( bytes memory encodedAddress - ) internal pure returns (address) { + ) internal pure { if (encodedAddress.length != 32) revert InvalidEVMAddress(encodedAddress); uint256 encodedAddressUint = abi.decode(encodedAddress, (uint256)); if (encodedAddressUint > type(uint160).max || encodedAddressUint < PRECOMPILE_SPACE) { revert InvalidEVMAddress(encodedAddress); } - return address(uint160(encodedAddressUint)); + } + + function _validateSVMAddress(bytes memory encodedAddress, bool mustBeNonZero) internal pure { + if (encodedAddress.length != 32) revert InvalidSVMAddress(encodedAddress); + if (mustBeNonZero) { + if (abi.decode(encodedAddress, (bytes32)) == bytes32(0)) { + revert InvalidSVMAddress(encodedAddress); + } + } } /// @notice Enum listing the possible message execution states within the offRamp contract. @@ -267,6 +275,9 @@ library Internal { // bytes4(keccak256("CCIP ChainFamilySelector EVM")); bytes4 public constant CHAIN_FAMILY_SELECTOR_EVM = 0x2812d52c; + // bytes4(keccak256("CCIP ChainFamilySelector SVM")); + bytes4 public constant CHAIN_FAMILY_SELECTOR_SVM = 0x1e10bdc4; + /// @dev Holds a merkle root and interval for a source chain so that an array of these can be passed in the CommitReport. /// @dev RMN depends on this struct, if changing, please notify the RMN maintainers. /// @dev inefficient struct packing intentionally chosen to maintain order of specificity. Not a storage struct so impact is minimal. diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol index c598eedc0d1..242b513cf5a 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.applyDestChainConfigUpdates.t.sol @@ -109,16 +109,4 @@ contract FeeQuoter_applyDestChainConfigUpdates is FeeQuoterSetup { ); s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); } - - function test_RevertWhen_InvalidChainFamilySelector() public { - FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); - FeeQuoter.DestChainConfigArgs memory destChainConfigArg = destChainConfigArgs[0]; - - destChainConfigArg.destChainConfig.chainFamilySelector = bytes4(uint32(1)); - - vm.expectRevert( - abi.encodeWithSelector(FeeQuoter.InvalidDestChainConfig.selector, destChainConfigArg.destChainSelector) - ); - s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); - } } diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol index 40db39dcb87..67ff5ae4ede 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.getValidatedFee.t.sol @@ -205,13 +205,24 @@ contract FeeQuoter_getValidatedFee is FeeQuoterFeeSetup { s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); } - // Reverts + function test_SolChainFamilySelector() public { + // Update config to enforce allowOutOfOrderExecution = true. + vm.stopPrank(); + vm.startPrank(OWNER); - function test_RevertWhen_DestinationChainNotEnabled() public { - vm.expectRevert(abi.encodeWithSelector(FeeQuoter.DestinationChainNotEnabled.selector, DEST_CHAIN_SELECTOR + 1)); - s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR + 1, _generateEmptyMessage()); + FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs(); + destChainConfigArgs[0].destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_SVM; + + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs); + vm.stopPrank(); + + Client.EVM2AnyMessage memory message = _generateEmptyMessage2SVM(); + + s_feeQuoter.getValidatedFee(DEST_CHAIN_SELECTOR, message); } + // Reverts + function test_RevertWhen_EnforceOutOfOrder() public { // Update config to enforce allowOutOfOrderExecution = true. vm.stopPrank(); diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol index 5e41f4ee223..ba2bde126c8 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseEVMExtraArgsFromBytes.t.sol @@ -5,7 +5,7 @@ import {FeeQuoter} from "../../FeeQuoter.sol"; import {Client} from "../../libraries/Client.sol"; import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; -contract FeeQuoter_parseEVMExtraArgsFromBytes is FeeQuoterSetup { +contract FeeQuoter_resolveGasLimitForDestination is FeeQuoterSetup { FeeQuoter.DestChainConfig private s_destChainConfig; function setUp() public virtual override { @@ -13,6 +13,14 @@ contract FeeQuoter_parseEVMExtraArgsFromBytes is FeeQuoterSetup { s_destChainConfig = _generateFeeQuoterDestChainConfigArgs()[0].destChainConfig; } + function test_EVMExtraArgsV1TagSelector() public view { + assertEq(Client.EVM_EXTRA_ARGS_V1_TAG, bytes4(keccak256("CCIP EVMExtraArgsV1"))); + } + + function test_EVMExtraArgsV2TagSelector() public view { + assertEq(Client.EVM_EXTRA_ARGS_V2_TAG, bytes4(keccak256("CCIP EVMExtraArgsV2"))); + } + function test_EVMExtraArgsV1() public view { Client.EVMExtraArgsV1 memory inputArgs = Client.EVMExtraArgsV1({gasLimit: GAS_LIMIT}); bytes memory inputExtraArgs = Client._argsToBytes(inputArgs); diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseSVMExtraArgsFromBytes.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseSVMExtraArgsFromBytes.t.sol new file mode 100644 index 00000000000..882fc17fe8f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.parseSVMExtraArgsFromBytes.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; + +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_parseSVMExtraArgsFromBytes is FeeQuoterSetup { + FeeQuoter.DestChainConfig private s_destChainConfig; + + /// @dev a Valid pubkey is one that is 32 bytes long, and that's it since no other validation can be performed + /// within the constraints of the EVM. + bytes32 internal constant VALID_SOL_PUBKEY = keccak256("SOL_PUBKEY"); + + function setUp() public virtual override { + super.setUp(); + s_destChainConfig = _generateFeeQuoterDestChainConfigArgs()[0].destChainConfig; + s_destChainConfig.enforceOutOfOrder = true; // Enforcing out of order execution for messages to SVM + s_destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_SVM; + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigs = new FeeQuoter.DestChainConfigArgs[](1); + destChainConfigs[0] = + FeeQuoter.DestChainConfigArgs({destChainSelector: DEST_CHAIN_SELECTOR, destChainConfig: s_destChainConfig}); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigs); + } + + function test_SVMExtraArgsV1TagSelector() public view { + assertEq(Client.SVM_EXTRA_EXTRA_ARGS_V1_TAG, bytes4(keccak256("CCIP SVMExtraArgsV1"))); + } + + function test_SVMExtraArgsV1() public view { + bytes32[] memory solAccounts = new bytes32[](1); + solAccounts[0] = VALID_SOL_PUBKEY; + + Client.SVMExtraArgsV1 memory inputArgs = Client.SVMExtraArgsV1({ + computeUnits: GAS_LIMIT, + accountIsWritableBitmap: 0, + tokenReceiver: bytes32(0), + allowOutOfOrderExecution: true, + accounts: solAccounts + }); + + bytes memory inputExtraArgs = Client._svmArgsToBytes(inputArgs); + + Client.SVMExtraArgsV1 memory expectedOutputArgs = Client.SVMExtraArgsV1({ + computeUnits: GAS_LIMIT, + accountIsWritableBitmap: 0, + tokenReceiver: bytes32(0), + allowOutOfOrderExecution: true, + accounts: solAccounts + }); + + vm.assertEq( + abi.encode(s_feeQuoter.parseSVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig)), + abi.encode(expectedOutputArgs) + ); + } + + // Reverts + function test_RevertWhen_ExtraArgsAreEmpty() public { + bytes memory inputExtraArgs = new bytes(0); + vm.expectRevert(FeeQuoter.InvalidExtraArgsData.selector); + s_feeQuoter.parseSVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } + + function test_RevertWhen_InvalidExtraArgsTag() public { + bytes memory inputExtraArgs = abi.encodeWithSelector(bytes4(0)); + + vm.expectRevert(FeeQuoter.InvalidExtraArgsTag.selector); + s_feeQuoter.parseSVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } + + function test_RevertWhen_SVMMessageGasLimitTooHigh() public { + Client.SVMExtraArgsV1 memory inputArgs = Client.SVMExtraArgsV1({ + computeUnits: s_destChainConfig.maxPerMsgGasLimit + 1, + accountIsWritableBitmap: 0, + tokenReceiver: bytes32(0), + allowOutOfOrderExecution: true, + accounts: new bytes32[](0) + }); + + bytes memory inputExtraArgs = Client._svmArgsToBytes(inputArgs); + + vm.expectRevert(FeeQuoter.MessageComputeUnitLimitTooHigh.selector); + s_feeQuoter.parseSVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } + + function test_RevertWhen_ExtraArgOutOfOrderExecutionIsFalse() public { + bytes memory inputExtraArgs = abi.encodeWithSelector( + Client.SVM_EXTRA_EXTRA_ARGS_V1_TAG, + Client.SVMExtraArgsV1({ + computeUnits: 1_000_000, + accountIsWritableBitmap: 0, + tokenReceiver: bytes32(0), + allowOutOfOrderExecution: false, // mismatch with enforcedOutOfOrder = true + accounts: new bytes32[](0) + }) + ); + + vm.expectRevert(FeeQuoter.ExtraArgOutOfOrderExecutionMustBeTrue.selector); + s_feeQuoter.parseSVMExtraArgsFromBytes(inputExtraArgs, s_destChainConfig); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processChainFamilySelector.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processChainFamilySelector.t.sol new file mode 100644 index 00000000000..e90034d3a9e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processChainFamilySelector.t.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {FeeQuoter} from "../../FeeQuoter.sol"; +import {Client} from "../../libraries/Client.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; + +contract FeeQuoter_processChainFamilySelector is FeeQuoterSetup { + uint64 internal constant SVM_SELECTOR = SOURCE_CHAIN_SELECTOR; + uint64 internal constant EVM_SELECTOR = DEST_CHAIN_SELECTOR; + uint64 internal constant INVALID_SELECTOR = 99; + + function setUp() public virtual override { + super.setUp(); + + // 1. Configure an EVM chain + FeeQuoter.DestChainConfig memory evmConfig; + evmConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_EVM; + evmConfig.defaultTxGasLimit = 500_000; + evmConfig.maxPerMsgGasLimit = 1_000_000; // Example + evmConfig.enforceOutOfOrder = false; // Example + + // 2. Configure an SVM chain + FeeQuoter.DestChainConfig memory svmConfig; + svmConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_SVM; + svmConfig.defaultTxGasLimit = 2_000_000; + svmConfig.maxPerMsgGasLimit = 3_000_000; // Example + svmConfig.enforceOutOfOrder = true; + + // Apply both configs + FeeQuoter.DestChainConfigArgs[] memory configs = new FeeQuoter.DestChainConfigArgs[](2); + configs[0] = FeeQuoter.DestChainConfigArgs({destChainSelector: EVM_SELECTOR, destChainConfig: evmConfig}); + configs[1] = FeeQuoter.DestChainConfigArgs({destChainSelector: SVM_SELECTOR, destChainConfig: svmConfig}); + s_feeQuoter.applyDestChainConfigUpdates(configs); + } + + // ---------------------------------------------------------------- + // TEST: EVM path + // ---------------------------------------------------------------- + function test_processChainFamilySelectorEVM() public { + Client.EVMExtraArgsV2 memory evmArgs = Client.EVMExtraArgsV2({gasLimit: 400_000, allowOutOfOrderExecution: true}); + bytes memory encodedEvmArgs = Client._argsToBytes(evmArgs); + + (bytes memory resultBytes, bool outOfOrder) = s_feeQuoter.processChainFamilySelector( + EVM_SELECTOR, + false, // isMessageWithTokenTransfer + encodedEvmArgs + ); + + assertEq(resultBytes, encodedEvmArgs, "Should return the same EVM-encoded bytes"); + assertEq(outOfOrder, evmArgs.allowOutOfOrderExecution, "Out-of-order mismatch"); + } + + // ---------------------------------------------------------------- + // TEST: SVM path + // ---------------------------------------------------------------- + function test_processChainFamilySelectorSVM_WithTokenTransfer() public { + // Construct an SVMExtraArgsV1 with a non-zero tokenReceiver + Client.SVMExtraArgsV1 memory svmArgs = Client.SVMExtraArgsV1({ + computeUnits: 1_500_000, // within the limit + accountIsWritableBitmap: 0, + tokenReceiver: bytes32("someReceiver"), + allowOutOfOrderExecution: true, + accounts: new bytes32[](0) + }); + bytes memory encodedSvmArgs = Client._svmArgsToBytes(svmArgs); + + (bytes memory resultBytes, bool outOfOrder) = s_feeQuoter.processChainFamilySelector( + SVM_SELECTOR, + true, // isMessageWithTokenTransfer + encodedSvmArgs + ); + + // The function should NOT revert since tokenReceiver != 0 + // Check that it returned the SVM-encoded bytes + assertEq(resultBytes, encodedSvmArgs, "Should return the same SVM-encoded bytes"); + // The function always returns `true` for outOfOrder on SVM + assertTrue(outOfOrder, "Out-of-order for SVM must be true"); + } + + function test_processChainFamilySelectorSVM_NoTokenTransfer() public { + Client.SVMExtraArgsV1 memory svmArgs = Client.SVMExtraArgsV1({ + computeUnits: 2_000_000, + accountIsWritableBitmap: 0, + tokenReceiver: bytes32(0), // zero is fine if not transferring tokens + allowOutOfOrderExecution: true, + accounts: new bytes32[](0) + }); + bytes memory encodedSvmArgs = Client._svmArgsToBytes(svmArgs); + + (bytes memory resultBytes, bool outOfOrder) = s_feeQuoter.processChainFamilySelector( + SVM_SELECTOR, + false, // no token transfer + encodedSvmArgs + ); + + // Should succeed with outOfOrder = true + assertEq(resultBytes, encodedSvmArgs, "Should return the SVM-encoded bytes"); + assertTrue(outOfOrder, "Out-of-order should be true for SVM"); + } + + // TEST: SVM path → reverts + + function test_processChainFamilySelector_RevertWhen_TokenTransferNoTokenReceiver() public { + // Construct an SVMExtraArgsV1 with tokenReceiver == 0 + Client.SVMExtraArgsV1 memory svmArgs = Client.SVMExtraArgsV1({ + computeUnits: 1_500_000, + accountIsWritableBitmap: 0, + tokenReceiver: bytes32(0), // <-- zero + allowOutOfOrderExecution: true, + accounts: new bytes32[](0) + }); + bytes memory encodedSvmArgs = Client._svmArgsToBytes(svmArgs); + + vm.expectRevert(FeeQuoter.InvalidTokenReceiver.selector); + + s_feeQuoter.processChainFamilySelector( + SVM_SELECTOR, + true, // token transfer + encodedSvmArgs + ); + } + + function test_processChainFamilySelector_RevertWhen_InvalidChainFamilySelector() public { + // Provide random extraArgs + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.InvalidChainFamilySelector.selector, bytes4(0))); + + s_feeQuoter.processChainFamilySelector(INVALID_SELECTOR, false, "0x1234"); + } +} diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol index 5041ddef667..92b59a7e592 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.processMessageArgs.t.sol @@ -128,6 +128,47 @@ contract FeeQuoter_processMessageArgs is FeeQuoterFeeSetup { ); } + function test_processMessageArgs_WithSVMExtraArgsV1() public { + // Apply the chain update to set the chain family selector to SOL + FeeQuoter.DestChainConfig memory s_destChainConfig = _generateFeeQuoterDestChainConfigArgs()[0].destChainConfig; + s_destChainConfig.chainFamilySelector = Internal.CHAIN_FAMILY_SELECTOR_SVM; + + FeeQuoter.DestChainConfigArgs[] memory destChainConfigs = new FeeQuoter.DestChainConfigArgs[](1); + destChainConfigs[0] = + FeeQuoter.DestChainConfigArgs({destChainSelector: DEST_CHAIN_SELECTOR, destChainConfig: s_destChainConfig}); + s_feeQuoter.applyDestChainConfigUpdates(destChainConfigs); + + bytes memory extraArgs = Client._svmArgsToBytes( + Client.SVMExtraArgsV1({ + computeUnits: 0, + accountIsWritableBitmap: 0, + tokenReceiver: bytes32(0), + allowOutOfOrderExecution: true, + accounts: new bytes32[](0) + }) + ); + + ( + /* uint256 msgFeeJuels */ + , + bool isOutOfOrderExecution, + bytes memory convertedExtraArgs, + /* destExecDataPerToken */ + ) = s_feeQuoter.processMessageArgs( + DEST_CHAIN_SELECTOR, + s_sourceTokens[0], + 0, + extraArgs, + new Internal.EVM2AnyTokenTransfer[](0), + new Client.EVMTokenAmount[](0) + ); + + assertTrue(isOutOfOrderExecution); + assertEq( + convertedExtraArgs, Client._svmArgsToBytes(s_feeQuoter.parseSVMExtraArgsFromBytes(extraArgs, s_destChainConfig)) + ); + } + // Reverts function test_RevertWhen_processMessageArgs_MessageFeeTooHigh() public { diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol index d11d8baddef..ae4e4766bd9 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.validateDestFamilyAddress.t.sol @@ -1,42 +1,49 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.24; +import {FeeQuoter} from "../../FeeQuoter.sol"; import {Internal} from "../../libraries/Internal.sol"; import {FeeQuoterSetup} from "./FeeQuoterSetup.t.sol"; contract FeeQuoter_validateDestFamilyAddress is FeeQuoterSetup { function test_ValidEVMAddress() public view { bytes memory encodedAddress = abi.encode(address(10000)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, encodedAddress); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, encodedAddress, 0); } - function test_ValidNonEVMAddress() public view { - s_feeQuoter.validateDestFamilyAddress(bytes4(uint32(1)), abi.encode(type(uint208).max)); + function test_ValidSVMAddress() public view { + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_SVM, abi.encode(type(uint208).max), 0); } // Reverts + function test_RevertWhen_InvalidChainFamilySelector() public { + bytes4 selector = bytes4(0xdeadbeef); + bytes memory encodedAddress = abi.encode(address(10000)); + vm.expectRevert(abi.encodeWithSelector(FeeQuoter.InvalidChainFamilySelector.selector, selector)); + s_feeQuoter.validateDestFamilyAddress(selector, encodedAddress, 0); + } function test_RevertWhen_InvalidEVMAddress() public { bytes memory invalidAddress = abi.encode(type(uint208).max); vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress, 0); } function test_RevertWhen_InvalidEVMAddressEncodePacked() public { bytes memory invalidAddress = abi.encodePacked(address(234)); vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress, 0); } function test_RevertWhen_InvalidEVMAddressPrecompiles() public { for (uint160 i = 0; i < Internal.PRECOMPILE_SPACE; ++i) { bytes memory invalidAddress = abi.encode(address(i)); vm.expectRevert(abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, invalidAddress)); - s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress); + s_feeQuoter.validateDestFamilyAddress(Internal.CHAIN_FAMILY_SELECTOR_EVM, invalidAddress, 0); } s_feeQuoter.validateDestFamilyAddress( - Internal.CHAIN_FAMILY_SELECTOR_EVM, abi.encode(address(uint160(Internal.PRECOMPILE_SPACE))) + Internal.CHAIN_FAMILY_SELECTOR_EVM, abi.encode(address(uint160(Internal.PRECOMPILE_SPACE))), 0 ); } } diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol index 006e3251467..5bf9b845eae 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol @@ -348,6 +348,25 @@ contract FeeQuoterFeeSetup is FeeQuoterSetup { }); } + // Used to generate a message with a specific extraArgs tag for SVM + function _generateEmptyMessage2SVM() public view returns (Client.EVM2AnyMessage memory) { + return Client.EVM2AnyMessage({ + receiver: abi.encode(OWNER), + data: "", + tokenAmounts: new Client.EVMTokenAmount[](0), + feeToken: s_sourceFeeToken, + extraArgs: Client._svmArgsToBytes( + Client.SVMExtraArgsV1({ + computeUnits: GAS_LIMIT, + accountIsWritableBitmap: 0, + allowOutOfOrderExecution: true, + tokenReceiver: bytes32(0), + accounts: new bytes32[](0) + }) + ) + }); + } + function _generateSingleTokenMessage( address token, uint256 amount diff --git a/contracts/src/v0.8/ccip/test/helpers/FeeQuoterHelper.sol b/contracts/src/v0.8/ccip/test/helpers/FeeQuoterHelper.sol index acee4dfda2f..6e4233b7d1f 100644 --- a/contracts/src/v0.8/ccip/test/helpers/FeeQuoterHelper.sol +++ b/contracts/src/v0.8/ccip/test/helpers/FeeQuoterHelper.sol @@ -82,8 +82,27 @@ contract FeeQuoterHelper is FeeQuoter { ); } - function validateDestFamilyAddress(bytes4 chainFamilySelector, bytes memory destAddress) external pure { - _validateDestFamilyAddress(chainFamilySelector, destAddress); + function parseSVMExtraArgsFromBytes( + bytes calldata extraArgs, + DestChainConfig memory destChainConfig + ) external pure returns (Client.SVMExtraArgsV1 memory) { + return _parseSVMExtraArgsFromBytes(extraArgs, destChainConfig.maxPerMsgGasLimit, destChainConfig.enforceOutOfOrder); + } + + function processChainFamilySelector( + uint64 chainFamilySelector, + bool isMessageWithTokenTransfer, + bytes calldata extraArgs + ) external view returns (bytes memory, bool) { + return _processChainFamilySelector(chainFamilySelector, isMessageWithTokenTransfer, extraArgs); + } + + function validateDestFamilyAddress( + bytes4 chainFamilySelector, + bytes memory destAddress, + uint256 gasLimit + ) external pure { + _validateDestFamilyAddress(chainFamilySelector, destAddress, gasLimit); } function calculateRebasedValue( @@ -93,4 +112,11 @@ contract FeeQuoterHelper is FeeQuoter { ) external pure returns (uint224) { return _calculateRebasedValue(dataFeedDecimal, tokenDecimal, feedValue); } + + function resolveGasLimitForDestination( + bytes calldata extraArgs, + DestChainConfig memory destChainConfig + ) external pure returns (uint256 gasLimit) { + return _resolveGasLimitForDestination(extraArgs, destChainConfig); + } } diff --git a/core/capabilities/ccip/ccipevm/extraargscodec.go b/core/capabilities/ccip/ccipevm/extraargscodec.go new file mode 100644 index 00000000000..8cd8bda48f7 --- /dev/null +++ b/core/capabilities/ccip/ccipevm/extraargscodec.go @@ -0,0 +1,16 @@ +package ccipevm + +import ( + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" +) + +type ExtraArgsCodec struct{} + +func NewExtraArgsCodec() ExtraArgsCodec { + return ExtraArgsCodec{} +} + +func (ExtraArgsCodec) DecodeExtraData(extraArgs cciptypes.Bytes, sourceChainSelector cciptypes.ChainSelector) (map[string]any, error) { + // Not implemented and not return error + return nil, nil +} diff --git a/core/capabilities/ccip/oraclecreator/plugin.go b/core/capabilities/ccip/oraclecreator/plugin.go index d4c5596aeaf..f791a804586 100644 --- a/core/capabilities/ccip/oraclecreator/plugin.go +++ b/core/capabilities/ccip/oraclecreator/plugin.go @@ -263,6 +263,7 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter( ccipreaderpkg.OCR3ConfigWithMeta(config), ccipevm.NewCommitPluginCodecV1(), ccipevm.NewMessageHasherV1(i.lggr.Named("MessageHasherV1")), + ccipevm.NewExtraArgsCodec(), i.homeChainReader, i.homeChainSelector, contractReaders, @@ -285,6 +286,7 @@ func (i *pluginOracleCreator) createFactoryAndTransmitter( ccipreaderpkg.OCR3ConfigWithMeta(config), ccipevm.NewExecutePluginCodecV1(), ccipevm.NewMessageHasherV1(i.lggr.Named("MessageHasherV1")), + ccipevm.NewExtraArgsCodec(), i.homeChainReader, ccipevm.NewEVMTokenDataEncoder(), ccipevm.NewGasEstimateProvider(), diff --git a/core/chains/evm/config/chain_scoped_transactions.go b/core/chains/evm/config/chain_scoped_transactions.go index 8cddce20e65..3da171c98f0 100644 --- a/core/chains/evm/config/chain_scoped_transactions.go +++ b/core/chains/evm/config/chain_scoped_transactions.go @@ -39,6 +39,31 @@ func (t *transactionsConfig) MaxQueued() uint64 { return uint64(*t.c.MaxQueued) } +func (t *transactionsConfig) TransactionManagerV2() TransactionManagerV2 { + return &transactionManagerV2Config{c: t.c.TransactionManagerV2} +} + +type transactionManagerV2Config struct { + c toml.TransactionManagerV2Config +} + +func (t *transactionManagerV2Config) Enabled() bool { + return *t.c.Enabled +} + +func (t *transactionManagerV2Config) BlockTime() *time.Duration { + d := t.c.BlockTime.Duration() + return &d +} + +func (t *transactionManagerV2Config) CustomURL() *url.URL { + return t.c.CustomURL.URL() +} + +func (t *transactionManagerV2Config) DualBroadcast() *bool { + return t.c.DualBroadcast +} + func (t *transactionsConfig) AutoPurge() AutoPurgeConfig { return &autoPurgeConfig{c: t.c.AutoPurge} } diff --git a/core/chains/evm/config/config.go b/core/chains/evm/config/config.go index fbaf1ff6dda..c76cb6953e5 100644 --- a/core/chains/evm/config/config.go +++ b/core/chains/evm/config/config.go @@ -111,6 +111,7 @@ type Transactions interface { MaxInFlight() uint32 MaxQueued() uint64 AutoPurge() AutoPurgeConfig + TransactionManagerV2() TransactionManagerV2 } type AutoPurgeConfig interface { @@ -120,6 +121,13 @@ type AutoPurgeConfig interface { DetectionApiUrl() *url.URL } +type TransactionManagerV2 interface { + Enabled() bool + BlockTime() *time.Duration + CustomURL() *url.URL + DualBroadcast() *bool +} + type GasEstimator interface { BlockHistory() BlockHistory FeeHistory() FeeHistory diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index 807e1141791..7a3dddcb1de 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -6,6 +6,7 @@ import ( "net/url" "slices" "strconv" + "time" "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/pelletier/go-toml/v2" @@ -471,6 +472,27 @@ func (c *Chain) ValidateConfig() (err error) { return } +func (c *Transactions) ValidateConfig() (err error) { + if c.TransactionManagerV2.Enabled != nil && *c.TransactionManagerV2.Enabled && + c.TransactionManagerV2.DualBroadcast != nil && *c.TransactionManagerV2.DualBroadcast { + if c.TransactionManagerV2.CustomURL == nil { + err = multierr.Append(err, commonconfig.ErrMissing{Name: "TransactionManagerV2.CustomURL", Msg: "must be set if DualBroadcast is enabled"}) + } + if c.AutoPurge.Enabled != nil && !*c.AutoPurge.Enabled { + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "AutoPurge.Enabled", Value: false, Msg: "cannot be false if DualBroadcast is enabled"}) + } + if c.AutoPurge.DetectionApiUrl == nil { + err = multierr.Append(err, commonconfig.ErrMissing{Name: "AutoPurge.DetectionApiUrl", Msg: "must be set if DualBroadcast is enabled"}) + } + if c.AutoPurge.Threshold == nil { + err = multierr.Append(err, commonconfig.ErrMissing{Name: "AutoPurge.Threshold", Msg: "needs to be set if auto-purge feature is enabled"}) + } else if *c.AutoPurge.Threshold == 0 { + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "AutoPurge.Threshold", Value: 0, Msg: "cannot be 0 if auto-purge feature is enabled"}) + } + } + return +} + type Transactions struct { Enabled *bool ForwardersEnabled *bool @@ -480,7 +502,8 @@ type Transactions struct { ReaperThreshold *commonconfig.Duration ResendAfterThreshold *commonconfig.Duration - AutoPurge AutoPurgeConfig `toml:",omitempty"` + AutoPurge AutoPurgeConfig `toml:",omitempty"` + TransactionManagerV2 TransactionManagerV2Config `toml:",omitempty"` } func (t *Transactions) setFrom(f *Transactions) { @@ -506,6 +529,7 @@ func (t *Transactions) setFrom(f *Transactions) { t.ResendAfterThreshold = v } t.AutoPurge.setFrom(&f.AutoPurge) + t.TransactionManagerV2.setFrom(&f.TransactionManagerV2) } type AutoPurgeConfig struct { @@ -530,6 +554,41 @@ func (a *AutoPurgeConfig) setFrom(f *AutoPurgeConfig) { } } +type TransactionManagerV2Config struct { + Enabled *bool `toml:",omitempty"` + BlockTime *commonconfig.Duration `toml:",omitempty"` + CustomURL *commonconfig.URL `toml:",omitempty"` + DualBroadcast *bool `toml:",omitempty"` +} + +func (t *TransactionManagerV2Config) setFrom(f *TransactionManagerV2Config) { + if v := f.Enabled; v != nil { + t.Enabled = f.Enabled + } + if v := f.BlockTime; v != nil { + t.BlockTime = f.BlockTime + } + if v := f.CustomURL; v != nil { + t.CustomURL = f.CustomURL + } + if v := f.DualBroadcast; v != nil { + t.DualBroadcast = f.DualBroadcast + } +} + +func (t *TransactionManagerV2Config) ValidateConfig() (err error) { + if t.Enabled != nil && *t.Enabled { + if t.BlockTime == nil { + err = multierr.Append(err, commonconfig.ErrMissing{Name: "BlockTime", Msg: "must be set if TransactionManagerV2 feature is enabled"}) + return + } + if t.BlockTime.Duration() < 2*time.Second { + err = multierr.Append(err, commonconfig.ErrInvalid{Name: "BlockTime", Msg: "must be equal to or greater than 2 seconds"}) + } + } + return +} + type OCR2 struct { Automation Automation `toml:",omitempty"` } diff --git a/core/chains/evm/config/toml/defaults/fallback.toml b/core/chains/evm/config/toml/defaults/fallback.toml index d2a6f0e4a2d..4b6caa20787 100644 --- a/core/chains/evm/config/toml/defaults/fallback.toml +++ b/core/chains/evm/config/toml/defaults/fallback.toml @@ -30,6 +30,9 @@ ResendAfterThreshold = '1m' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true diff --git a/core/chains/evm/keystore/eth.go b/core/chains/evm/keystore/eth.go index ff71e0a4f18..9c0986d9c3d 100644 --- a/core/chains/evm/keystore/eth.go +++ b/core/chains/evm/keystore/eth.go @@ -13,5 +13,6 @@ type Eth interface { CheckEnabled(ctx context.Context, address common.Address, chainID *big.Int) error EnabledAddressesForChain(ctx context.Context, chainID *big.Int) (addresses []common.Address, err error) SignTx(ctx context.Context, fromAddress common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) + SignMessage(ctx context.Context, address common.Address, message []byte) ([]byte, error) SubscribeToKeyChanges(ctx context.Context) (ch chan struct{}, unsub func()) } diff --git a/core/chains/evm/keystore/mocks/eth.go b/core/chains/evm/keystore/mocks/eth.go index aefe6ff7548..96125e66936 100644 --- a/core/chains/evm/keystore/mocks/eth.go +++ b/core/chains/evm/keystore/mocks/eth.go @@ -133,6 +133,66 @@ func (_c *Eth_EnabledAddressesForChain_Call) RunAndReturn(run func(context.Conte return _c } +// SignMessage provides a mock function with given fields: ctx, address, message +func (_m *Eth) SignMessage(ctx context.Context, address common.Address, message []byte) ([]byte, error) { + ret := _m.Called(ctx, address, message) + + if len(ret) == 0 { + panic("no return value specified for SignMessage") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, []byte) ([]byte, error)); ok { + return rf(ctx, address, message) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, []byte) []byte); ok { + r0 = rf(ctx, address, message) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, []byte) error); ok { + r1 = rf(ctx, address, message) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Eth_SignMessage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SignMessage' +type Eth_SignMessage_Call struct { + *mock.Call +} + +// SignMessage is a helper method to define mock.On call +// - ctx context.Context +// - address common.Address +// - message []byte +func (_e *Eth_Expecter) SignMessage(ctx interface{}, address interface{}, message interface{}) *Eth_SignMessage_Call { + return &Eth_SignMessage_Call{Call: _e.mock.On("SignMessage", ctx, address, message)} +} + +func (_c *Eth_SignMessage_Call) Run(run func(ctx context.Context, address common.Address, message []byte)) *Eth_SignMessage_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].([]byte)) + }) + return _c +} + +func (_c *Eth_SignMessage_Call) Return(_a0 []byte, _a1 error) *Eth_SignMessage_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Eth_SignMessage_Call) RunAndReturn(run func(context.Context, common.Address, []byte) ([]byte, error)) *Eth_SignMessage_Call { + _c.Call.Return(run) + return _c +} + // SignTx provides a mock function with given fields: ctx, fromAddress, tx, chainID func (_m *Eth) SignTx(ctx context.Context, fromAddress common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { ret := _m.Called(ctx, fromAddress, tx, chainID) diff --git a/core/chains/evm/txm/attempt_builder.go b/core/chains/evm/txm/attempt_builder.go new file mode 100644 index 00000000000..16ed0f1a86a --- /dev/null +++ b/core/chains/evm/txm/attempt_builder.go @@ -0,0 +1,161 @@ +package txm + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "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/txm/types" +) + +type AttemptBuilderKeystore interface { + SignTx(ctx context.Context, fromAddress common.Address, tx *evmtypes.Transaction, chainID *big.Int) (*evmtypes.Transaction, error) +} + +type attemptBuilder struct { + gas.EvmFeeEstimator + chainID *big.Int + priceMaxKey func(common.Address) *assets.Wei + keystore AttemptBuilderKeystore +} + +func NewAttemptBuilder(chainID *big.Int, priceMaxKey func(common.Address) *assets.Wei, estimator gas.EvmFeeEstimator, keystore AttemptBuilderKeystore) *attemptBuilder { + return &attemptBuilder{ + chainID: chainID, + priceMaxKey: priceMaxKey, + EvmFeeEstimator: estimator, + keystore: keystore, + } +} + +func (a *attemptBuilder) NewAttempt(ctx context.Context, lggr logger.Logger, tx *types.Transaction, dynamic bool) (*types.Attempt, error) { + fee, estimatedGasLimit, err := a.EvmFeeEstimator.GetFee(ctx, tx.Data, tx.SpecifiedGasLimit, a.priceMaxKey(tx.FromAddress), &tx.FromAddress, &tx.ToAddress) + if err != nil { + return nil, err + } + txType := evmtypes.LegacyTxType + if dynamic { + txType = evmtypes.DynamicFeeTxType + } + return a.newCustomAttempt(ctx, tx, fee, estimatedGasLimit, byte(txType), lggr) +} + +func (a *attemptBuilder) NewBumpAttempt(ctx context.Context, lggr logger.Logger, tx *types.Transaction, previousAttempt types.Attempt) (*types.Attempt, error) { + bumpedFee, bumpedFeeLimit, err := a.EvmFeeEstimator.BumpFee(ctx, previousAttempt.Fee, tx.SpecifiedGasLimit, a.priceMaxKey(tx.FromAddress), nil) + if err != nil { + return nil, err + } + return a.newCustomAttempt(ctx, tx, bumpedFee, bumpedFeeLimit, previousAttempt.Type, lggr) +} + +func (a *attemptBuilder) newCustomAttempt( + ctx context.Context, + tx *types.Transaction, + fee gas.EvmFee, + estimatedGasLimit uint64, + txType byte, + lggr logger.Logger, +) (attempt *types.Attempt, err error) { + switch txType { + case 0x0: + if fee.GasPrice == nil { + err = fmt.Errorf("tried to create attempt of type %v for txID: %v but estimator did not return legacy fee", txType, tx.ID) + logger.Sugared(lggr).AssumptionViolation(err.Error()) + return + } + return a.newLegacyAttempt(ctx, tx, fee.GasPrice, estimatedGasLimit) + case 0x2: + if !fee.ValidDynamic() { + err = fmt.Errorf("tried to create attempt of type %v for txID: %v but estimator did not return dynamic fee", txType, tx.ID) + logger.Sugared(lggr).AssumptionViolation(err.Error()) + return + } + return a.newDynamicFeeAttempt(ctx, tx, fee.DynamicFee, estimatedGasLimit) + default: + return nil, fmt.Errorf("cannot build attempt, unrecognized transaction type: %v", txType) + } +} + +func (a *attemptBuilder) newLegacyAttempt(ctx context.Context, tx *types.Transaction, gasPrice *assets.Wei, estimatedGasLimit uint64) (*types.Attempt, error) { + var data []byte + var toAddress common.Address + value := big.NewInt(0) + if !tx.IsPurgeable { + data = tx.Data + toAddress = tx.ToAddress + value = tx.Value + } + if tx.Nonce == nil { + return nil, fmt.Errorf("failed to create attempt for txID: %v: nonce empty", tx.ID) + } + legacyTx := evmtypes.LegacyTx{ + Nonce: *tx.Nonce, + To: &toAddress, + Value: value, + Gas: estimatedGasLimit, + GasPrice: gasPrice.ToInt(), + Data: data, + } + + signedTx, err := a.keystore.SignTx(ctx, tx.FromAddress, evmtypes.NewTx(&legacyTx), a.chainID) + if err != nil { + return nil, fmt.Errorf("failed to sign attempt for txID: %v, err: %w", tx.ID, err) + } + + attempt := &types.Attempt{ + TxID: tx.ID, + Fee: gas.EvmFee{GasPrice: gasPrice}, + Hash: signedTx.Hash(), + GasLimit: estimatedGasLimit, + Type: evmtypes.LegacyTxType, + SignedTransaction: signedTx, + } + + return attempt, nil +} + +func (a *attemptBuilder) newDynamicFeeAttempt(ctx context.Context, tx *types.Transaction, dynamicFee gas.DynamicFee, estimatedGasLimit uint64) (*types.Attempt, error) { + var data []byte + var toAddress common.Address + value := big.NewInt(0) + if !tx.IsPurgeable { + data = tx.Data + toAddress = tx.ToAddress + value = tx.Value + } + if tx.Nonce == nil { + return nil, fmt.Errorf("failed to create attempt for txID: %v: nonce empty", tx.ID) + } + dynamicTx := evmtypes.DynamicFeeTx{ + Nonce: *tx.Nonce, + To: &toAddress, + Value: value, + Gas: estimatedGasLimit, + GasFeeCap: dynamicFee.GasFeeCap.ToInt(), + GasTipCap: dynamicFee.GasTipCap.ToInt(), + Data: data, + } + + signedTx, err := a.keystore.SignTx(ctx, tx.FromAddress, evmtypes.NewTx(&dynamicTx), a.chainID) + if err != nil { + return nil, fmt.Errorf("failed to sign attempt for txID: %v, err: %w", tx.ID, err) + } + + attempt := &types.Attempt{ + TxID: tx.ID, + Fee: gas.EvmFee{DynamicFee: gas.DynamicFee{GasFeeCap: dynamicFee.GasFeeCap, GasTipCap: dynamicFee.GasTipCap}}, + Hash: signedTx.Hash(), + GasLimit: estimatedGasLimit, + Type: evmtypes.DynamicFeeTxType, + SignedTransaction: signedTx, + } + + return attempt, nil +} diff --git a/core/chains/evm/txm/attempt_builder_test.go b/core/chains/evm/txm/attempt_builder_test.go new file mode 100644 index 00000000000..65330cd39d7 --- /dev/null +++ b/core/chains/evm/txm/attempt_builder_test.go @@ -0,0 +1,97 @@ +package txm + +import ( + "math/big" + "testing" + + evmtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "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/keystore/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +func TestAttemptBuilder_newLegacyAttempt(t *testing.T) { + ks := mocks.NewEth(t) + ab := NewAttemptBuilder(testutils.FixtureChainID, nil, nil, ks) + address := testutils.NewAddress() + toAddress := testutils.NewAddress() + lggr := logger.Test(t) + var gasLimit uint64 = 100 + + t.Run("fails if GasPrice is nil", func(t *testing.T) { + tx := &types.Transaction{ID: 10, FromAddress: address} + _, err := ab.newCustomAttempt(tests.Context(t), tx, gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(1), GasFeeCap: assets.NewWeiI(2)}}, gasLimit, evmtypes.LegacyTxType, lggr) + require.Error(t, err) + assert.Contains(t, err.Error(), "estimator did not return legacy fee") + }) + + t.Run("fails if tx doesn't have a nonce", func(t *testing.T) { + tx := &types.Transaction{ID: 10, FromAddress: address} + _, err := ab.newCustomAttempt(tests.Context(t), tx, gas.EvmFee{GasPrice: assets.NewWeiI(25)}, gasLimit, evmtypes.LegacyTxType, lggr) + require.Error(t, err) + assert.Contains(t, err.Error(), "nonce empty") + }) + + t.Run("creates attempt with fields", func(t *testing.T) { + var nonce uint64 = 77 + tx := &types.Transaction{ID: 10, FromAddress: address, Nonce: &nonce} + legacyTx := evmtypes.NewTx(&evmtypes.LegacyTx{Nonce: nonce, To: &toAddress, Gas: gasLimit, GasPrice: big.NewInt(25)}) + ks.On("SignTx", mock.Anything, mock.Anything, mock.Anything, testutils.FixtureChainID).Return(legacyTx, nil).Once() + a, err := ab.newCustomAttempt(tests.Context(t), tx, gas.EvmFee{GasPrice: assets.NewWeiI(25)}, gasLimit, evmtypes.LegacyTxType, lggr) + require.NoError(t, err) + assert.Equal(t, tx.ID, a.TxID) + assert.Equal(t, evmtypes.LegacyTxType, int(a.Type)) + assert.NotNil(t, a.Fee.GasPrice) + assert.Equal(t, "25 wei", a.Fee.GasPrice.String()) + assert.Nil(t, a.Fee.GasTipCap) + assert.Nil(t, a.Fee.GasFeeCap) + assert.Equal(t, gasLimit, a.GasLimit) + }) +} + +func TestAttemptBuilder_newDynamicFeeAttempt(t *testing.T) { + ks := mocks.NewEth(t) + ab := NewAttemptBuilder(testutils.FixtureChainID, nil, nil, ks) + address := testutils.NewAddress() + toAddress := testutils.NewAddress() + lggr := logger.Test(t) + var gasLimit uint64 = 100 + + t.Run("fails if DynamicFee is invalid", func(t *testing.T) { + tx := &types.Transaction{ID: 10, FromAddress: address} + _, err := ab.newCustomAttempt(tests.Context(t), tx, gas.EvmFee{GasPrice: assets.NewWeiI(1)}, gasLimit, evmtypes.DynamicFeeTxType, lggr) + require.Error(t, err) + assert.Contains(t, err.Error(), "estimator did not return dynamic fee") + }) + + t.Run("fails if tx doesn't have a nonce", func(t *testing.T) { + tx := &types.Transaction{ID: 10, FromAddress: address} + _, err := ab.newCustomAttempt(tests.Context(t), tx, gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(1), GasFeeCap: assets.NewWeiI(2)}}, gasLimit, evmtypes.DynamicFeeTxType, lggr) + require.Error(t, err) + assert.Contains(t, err.Error(), "nonce empty") + }) + + t.Run("creates attempt with fields", func(t *testing.T) { + var nonce uint64 = 77 + tx := &types.Transaction{ID: 10, FromAddress: address, Nonce: &nonce} + legacyTx := evmtypes.NewTx(&evmtypes.LegacyTx{Nonce: nonce, To: &toAddress, Gas: gasLimit, GasPrice: big.NewInt(25)}) + ks.On("SignTx", mock.Anything, mock.Anything, mock.Anything, testutils.FixtureChainID).Return(legacyTx, nil).Once() + a, err := ab.newCustomAttempt(tests.Context(t), tx, gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(1), GasFeeCap: assets.NewWeiI(2)}}, gasLimit, evmtypes.DynamicFeeTxType, lggr) + require.NoError(t, err) + assert.Equal(t, tx.ID, a.TxID) + assert.Equal(t, evmtypes.DynamicFeeTxType, int(a.Type)) + assert.Equal(t, "1 wei", a.Fee.DynamicFee.GasTipCap.String()) + assert.Equal(t, "2 wei", a.Fee.DynamicFee.GasFeeCap.String()) + assert.Nil(t, a.Fee.GasPrice) + assert.Equal(t, gasLimit, a.GasLimit) + }) +} diff --git a/core/chains/evm/txm/clientwrappers/chain_client.go b/core/chains/evm/txm/clientwrappers/chain_client.go new file mode 100644 index 00000000000..7638cc53443 --- /dev/null +++ b/core/chains/evm/txm/clientwrappers/chain_client.go @@ -0,0 +1,31 @@ +package clientwrappers + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +type ChainClient struct { + c client.Client +} + +func NewChainClient(client client.Client) *ChainClient { + return &ChainClient{c: client} +} + +func (c *ChainClient) NonceAt(ctx context.Context, address common.Address, blockNumber *big.Int) (uint64, error) { + return c.c.NonceAt(ctx, address, blockNumber) +} + +func (c *ChainClient) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) { + return c.c.PendingNonceAt(ctx, address) +} + +func (c *ChainClient) SendTransaction(ctx context.Context, _ *types.Transaction, attempt *types.Attempt) error { + return c.c.SendTransaction(ctx, attempt.SignedTransaction) +} diff --git a/core/chains/evm/txm/clientwrappers/dual_broadcast_client.go b/core/chains/evm/txm/clientwrappers/dual_broadcast_client.go new file mode 100644 index 00000000000..481c26cbc2b --- /dev/null +++ b/core/chains/evm/txm/clientwrappers/dual_broadcast_client.go @@ -0,0 +1,128 @@ +package clientwrappers + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net/http" + "net/url" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +type DualBroadcastClientKeystore interface { + SignMessage(ctx context.Context, address common.Address, message []byte) ([]byte, error) +} + +type DualBroadcastClient struct { + c client.Client + keystore DualBroadcastClientKeystore + customURL *url.URL +} + +func NewDualBroadcastClient(c client.Client, keystore DualBroadcastClientKeystore, customURL *url.URL) *DualBroadcastClient { + return &DualBroadcastClient{ + c: c, + keystore: keystore, + customURL: customURL, + } +} + +func (d *DualBroadcastClient) NonceAt(ctx context.Context, address common.Address, blockNumber *big.Int) (uint64, error) { + return d.c.NonceAt(ctx, address, blockNumber) +} + +func (d *DualBroadcastClient) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) { + body := []byte(fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["%s","pending"], "id":1}`, address.String())) + response, err := d.signAndPostMessage(ctx, address, body, "") + if err != nil { + return 0, err + } + + nonce, err := hexutil.DecodeUint64(response) + if err != nil { + return 0, fmt.Errorf("failed to decode response %v into uint64: %w", response, err) + } + return nonce, nil +} + +func (d *DualBroadcastClient) SendTransaction(ctx context.Context, tx *types.Transaction, attempt *types.Attempt) error { + meta, err := tx.GetMeta() + if err != nil { + return err + } + + if meta != nil && meta.DualBroadcast != nil && *meta.DualBroadcast && !tx.IsPurgeable { + data, err := attempt.SignedTransaction.MarshalBinary() + if err != nil { + return err + } + params := "" + if meta.DualBroadcastParams != nil { + params = *meta.DualBroadcastParams + } + body := []byte(fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["%s"], "id":1}`, hexutil.Encode(data))) + _, err = d.signAndPostMessage(ctx, tx.FromAddress, body, params) + return err + } + + return d.c.SendTransaction(ctx, attempt.SignedTransaction) +} + +func (d *DualBroadcastClient) signAndPostMessage(ctx context.Context, address common.Address, body []byte, urlParams string) (result string, err error) { + bodyReader := bytes.NewReader(body) + postReq, err := http.NewRequestWithContext(ctx, http.MethodPost, d.customURL.String()+"?"+urlParams, bodyReader) + if err != nil { + return + } + + hashedBody := crypto.Keccak256Hash(body).Hex() + signedMessage, err := d.keystore.SignMessage(ctx, address, []byte(hashedBody)) + if err != nil { + return + } + + postReq.Header.Add("X-Flashbots-signature", address.String()+":"+hexutil.Encode(signedMessage)) + postReq.Header.Add("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(postReq) + if err != nil { + return result, fmt.Errorf("request %v failed: %w", postReq, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return result, fmt.Errorf("request %v failed with status: %d", postReq, resp.StatusCode) + } + + keyJSON, err := io.ReadAll(resp.Body) + if err != nil { + return + } + var response postResponse + err = json.Unmarshal(keyJSON, &response) + if err != nil { + return result, fmt.Errorf("failed to unmarshal response into struct: %w: %s", err, string(keyJSON)) + } + if response.Error.Message != "" { + return result, errors.New(response.Error.Message) + } + return response.Result, nil +} + +type postResponse struct { + Result string `json:"result,omitempty"` + Error postError +} + +type postError struct { + Message string `json:"message,omitempty"` +} diff --git a/core/chains/evm/txm/clientwrappers/geth_client.go b/core/chains/evm/txm/clientwrappers/geth_client.go new file mode 100644 index 00000000000..d97e5cfae35 --- /dev/null +++ b/core/chains/evm/txm/clientwrappers/geth_client.go @@ -0,0 +1,51 @@ +package clientwrappers + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +type GethClient struct { + *ethclient.Client +} + +func NewGethClient(client *ethclient.Client) *GethClient { + return &GethClient{ + Client: client, + } +} + +func (g *GethClient) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { + return g.Client.Client().BatchCallContext(ctx, b) +} + +func (g *GethClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + return g.Client.Client().CallContext(ctx, result, method, args...) +} + +func (g *GethClient) CallContract(ctx context.Context, message ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + var hex hexutil.Bytes + err := g.CallContext(ctx, &hex, "eth_call", client.ToBackwardCompatibleCallArg(message), client.ToBackwardCompatibleBlockNumArg(blockNumber)) + return hex, err +} + +func (g *GethClient) HeadByNumber(ctx context.Context, number *big.Int) (*evmtypes.Head, error) { + hexNumber := client.ToBlockNumArg(number) + args := []interface{}{hexNumber, false} + head := new(evmtypes.Head) + err := g.CallContext(ctx, head, "eth_getBlockByNumber", args...) + return head, err +} + +func (g *GethClient) SendTransaction(ctx context.Context, _ *types.Transaction, attempt *types.Attempt) error { + return g.Client.SendTransaction(ctx, attempt.SignedTransaction) +} diff --git a/core/chains/evm/txm/docs/TRANSACTION_MANAGER_V2.md b/core/chains/evm/txm/docs/TRANSACTION_MANAGER_V2.md new file mode 100644 index 00000000000..d408cc7d733 --- /dev/null +++ b/core/chains/evm/txm/docs/TRANSACTION_MANAGER_V2.md @@ -0,0 +1,14 @@ + +# Transaction Manager V2 + +## Configs +- `EIP1559`: enables EIP-1559 mode. This means the transaction manager will create and broadcast Dynamic attempts. Set this to false to broadcast Legacy transactions. +- `BlockTime`: controls the interval of the backfill loop. This dictates how frequently the transaction manager will check for confirmed transactions, rebroadcast stuck ones, and fill any nonce gaps. Transactions are getting confirmed only during new blocks so it's best if you set this to a value close to the block time. At least one RPC call is made during each BlockTime interval so the recommended minimum is 2s. A small jitter is applied so the timeout won't be exactly the same each time. +- `RetryBlockThreshold`: is the number of blocks to wait for a transaction stuck in the mempool before automatically rebroadcasting it with a new attempt. +- `EmptyTxLimitDefault`: sets default gas limit for empty transactions. Empty transactions are created in case there is a nonce gap or another stuck transaction in the mempool to fill a given nonce. These are empty transactions and they don't have any data or value. + +## Metrics +- `txm_num_broadcasted_transactions`: total number of successful broadcasted transactions. +- `txm_num_confirmed_transactions`: total number of confirmed transactions. Note that this can happen multiple times per transaction in the case of re-orgs. +- `txm_num_nonce_gaps`: total number of nonce gaps created that the transaction manager had to fill. +- `txm_time_until_tx_confirmed`: The amount of time elapsed from a transaction being broadcast to being included in a block. \ No newline at end of file diff --git a/core/chains/evm/txm/dummy_keystore.go b/core/chains/evm/txm/dummy_keystore.go new file mode 100644 index 00000000000..01816dfcbbd --- /dev/null +++ b/core/chains/evm/txm/dummy_keystore.go @@ -0,0 +1,64 @@ +package txm + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +type DummyKeystore struct { + privateKeyMap map[common.Address]*ecdsa.PrivateKey +} + +func NewKeystore() *DummyKeystore { + return &DummyKeystore{privateKeyMap: make(map[common.Address]*ecdsa.PrivateKey)} +} + +func (k *DummyKeystore) Add(privateKeyString string) error { + privateKey, err := crypto.HexToECDSA(privateKeyString) + if err != nil { + return err + } + + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("error casting public key: %v to ECDSA", publicKey) + } + + address := crypto.PubkeyToAddress(*publicKeyECDSA) + k.privateKeyMap[address] = privateKey + return nil +} + +func (k *DummyKeystore) SignTx(_ context.Context, fromAddress common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + if key, exists := k.privateKeyMap[fromAddress]; exists { + return types.SignTx(tx, types.LatestSignerForChainID(chainID), key) + } + return nil, fmt.Errorf("private key for address: %v not found", fromAddress) +} + +func (k *DummyKeystore) SignMessage(ctx context.Context, address common.Address, data []byte) ([]byte, error) { + key, exists := k.privateKeyMap[address] + if !exists { + return nil, fmt.Errorf("private key for address: %v not found", address) + } + signature, err := crypto.Sign(accounts.TextHash(data), key) + if err != nil { + return nil, fmt.Errorf("failed to sign message for address: %v", address) + } + return signature, nil +} + +func (k *DummyKeystore) EnabledAddressesForChain(_ context.Context, _ *big.Int) (addresses []common.Address, err error) { + for address := range k.privateKeyMap { + addresses = append(addresses, address) + } + return +} diff --git a/core/chains/evm/txm/metrics.go b/core/chains/evm/txm/metrics.go new file mode 100644 index 00000000000..5ccc711ef09 --- /dev/null +++ b/core/chains/evm/txm/metrics.go @@ -0,0 +1,93 @@ +package txm + +import ( + "context" + "fmt" + "math/big" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "go.opentelemetry.io/otel/metric" + + "github.com/smartcontractkit/chainlink-common/pkg/beholder" + "github.com/smartcontractkit/chainlink-common/pkg/metrics" +) + +var ( + promNumBroadcastedTxs = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "txm_num_broadcasted_transactions", + Help: "Total number of successful broadcasted transactions.", + }, []string{"chainID"}) + promNumConfirmedTxs = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "txm_num_confirmed_transactions", + Help: "Total number of confirmed transactions. Note that this can happen multiple times per transaction in the case of re-orgs or when filling the nonce for untracked transactions.", + }, []string{"chainID"}) + promNumNonceGaps = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "txm_num_nonce_gaps", + Help: "Total number of nonce gaps created that the transaction manager had to fill.", + }, []string{"chainID"}) + promTimeUntilTxConfirmed = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Name: "txm_time_until_tx_confirmed", + Help: "The amount of time elapsed from a transaction being broadcast to being included in a block.", + }, []string{"chainID"}) +) + +type txmMetrics struct { + metrics.Labeler + chainID *big.Int + numBroadcastedTxs metric.Int64Counter + numConfirmedTxs metric.Int64Counter + numNonceGaps metric.Int64Counter + timeUntilTxConfirmed metric.Float64Histogram +} + +func NewTxmMetrics(chainID *big.Int) (*txmMetrics, error) { + numBroadcastedTxs, err := beholder.GetMeter().Int64Counter("txm_num_broadcasted_transactions") + if err != nil { + return nil, fmt.Errorf("failed to register broadcasted txs number: %w", err) + } + + numConfirmedTxs, err := beholder.GetMeter().Int64Counter("txm_num_confirmed_transactions") + if err != nil { + return nil, fmt.Errorf("failed to register confirmed txs number: %w", err) + } + + numNonceGaps, err := beholder.GetMeter().Int64Counter("txm_num_nonce_gaps") + if err != nil { + return nil, fmt.Errorf("failed to register nonce gaps number: %w", err) + } + + timeUntilTxConfirmed, err := beholder.GetMeter().Float64Histogram("txm_time_until_tx_confirmed") + if err != nil { + return nil, fmt.Errorf("failed to register time until tx confirmed: %w", err) + } + + return &txmMetrics{ + chainID: chainID, + Labeler: metrics.NewLabeler().With("chainID", chainID.String()), + numBroadcastedTxs: numBroadcastedTxs, + numConfirmedTxs: numConfirmedTxs, + numNonceGaps: numNonceGaps, + timeUntilTxConfirmed: timeUntilTxConfirmed, + }, nil +} + +func (m *txmMetrics) IncrementNumBroadcastedTxs(ctx context.Context) { + promNumBroadcastedTxs.WithLabelValues(m.chainID.String()).Add(float64(1)) + m.numBroadcastedTxs.Add(ctx, 1) +} + +func (m *txmMetrics) IncrementNumConfirmedTxs(ctx context.Context, confirmedTransactions int) { + promNumConfirmedTxs.WithLabelValues(m.chainID.String()).Add(float64(confirmedTransactions)) + m.numConfirmedTxs.Add(ctx, int64(confirmedTransactions)) +} + +func (m *txmMetrics) IncrementNumNonceGaps(ctx context.Context) { + promNumNonceGaps.WithLabelValues(m.chainID.String()).Add(float64(1)) + m.numNonceGaps.Add(ctx, 1) +} + +func (m *txmMetrics) RecordTimeUntilTxConfirmed(ctx context.Context, duration float64) { + promTimeUntilTxConfirmed.WithLabelValues(m.chainID.String()).Observe(duration) + m.timeUntilTxConfirmed.Record(ctx, duration) +} diff --git a/core/chains/evm/txm/mocks/attempt_builder.go b/core/chains/evm/txm/mocks/attempt_builder.go new file mode 100644 index 00000000000..91961e5d420 --- /dev/null +++ b/core/chains/evm/txm/mocks/attempt_builder.go @@ -0,0 +1,161 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + logger "github.com/smartcontractkit/chainlink-common/pkg/logger" + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +// AttemptBuilder is an autogenerated mock type for the AttemptBuilder type +type AttemptBuilder struct { + mock.Mock +} + +type AttemptBuilder_Expecter struct { + mock *mock.Mock +} + +func (_m *AttemptBuilder) EXPECT() *AttemptBuilder_Expecter { + return &AttemptBuilder_Expecter{mock: &_m.Mock} +} + +// NewAttempt provides a mock function with given fields: _a0, _a1, _a2, _a3 +func (_m *AttemptBuilder) NewAttempt(_a0 context.Context, _a1 logger.Logger, _a2 *types.Transaction, _a3 bool) (*types.Attempt, error) { + ret := _m.Called(_a0, _a1, _a2, _a3) + + if len(ret) == 0 { + panic("no return value specified for NewAttempt") + } + + var r0 *types.Attempt + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, logger.Logger, *types.Transaction, bool) (*types.Attempt, error)); ok { + return rf(_a0, _a1, _a2, _a3) + } + if rf, ok := ret.Get(0).(func(context.Context, logger.Logger, *types.Transaction, bool) *types.Attempt); ok { + r0 = rf(_a0, _a1, _a2, _a3) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Attempt) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, logger.Logger, *types.Transaction, bool) error); ok { + r1 = rf(_a0, _a1, _a2, _a3) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AttemptBuilder_NewAttempt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NewAttempt' +type AttemptBuilder_NewAttempt_Call struct { + *mock.Call +} + +// NewAttempt is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 logger.Logger +// - _a2 *types.Transaction +// - _a3 bool +func (_e *AttemptBuilder_Expecter) NewAttempt(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}) *AttemptBuilder_NewAttempt_Call { + return &AttemptBuilder_NewAttempt_Call{Call: _e.mock.On("NewAttempt", _a0, _a1, _a2, _a3)} +} + +func (_c *AttemptBuilder_NewAttempt_Call) Run(run func(_a0 context.Context, _a1 logger.Logger, _a2 *types.Transaction, _a3 bool)) *AttemptBuilder_NewAttempt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(logger.Logger), args[2].(*types.Transaction), args[3].(bool)) + }) + return _c +} + +func (_c *AttemptBuilder_NewAttempt_Call) Return(_a0 *types.Attempt, _a1 error) *AttemptBuilder_NewAttempt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AttemptBuilder_NewAttempt_Call) RunAndReturn(run func(context.Context, logger.Logger, *types.Transaction, bool) (*types.Attempt, error)) *AttemptBuilder_NewAttempt_Call { + _c.Call.Return(run) + return _c +} + +// NewBumpAttempt provides a mock function with given fields: _a0, _a1, _a2, _a3 +func (_m *AttemptBuilder) NewBumpAttempt(_a0 context.Context, _a1 logger.Logger, _a2 *types.Transaction, _a3 types.Attempt) (*types.Attempt, error) { + ret := _m.Called(_a0, _a1, _a2, _a3) + + if len(ret) == 0 { + panic("no return value specified for NewBumpAttempt") + } + + var r0 *types.Attempt + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, logger.Logger, *types.Transaction, types.Attempt) (*types.Attempt, error)); ok { + return rf(_a0, _a1, _a2, _a3) + } + if rf, ok := ret.Get(0).(func(context.Context, logger.Logger, *types.Transaction, types.Attempt) *types.Attempt); ok { + r0 = rf(_a0, _a1, _a2, _a3) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Attempt) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, logger.Logger, *types.Transaction, types.Attempt) error); ok { + r1 = rf(_a0, _a1, _a2, _a3) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AttemptBuilder_NewBumpAttempt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NewBumpAttempt' +type AttemptBuilder_NewBumpAttempt_Call struct { + *mock.Call +} + +// NewBumpAttempt is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 logger.Logger +// - _a2 *types.Transaction +// - _a3 types.Attempt +func (_e *AttemptBuilder_Expecter) NewBumpAttempt(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}) *AttemptBuilder_NewBumpAttempt_Call { + return &AttemptBuilder_NewBumpAttempt_Call{Call: _e.mock.On("NewBumpAttempt", _a0, _a1, _a2, _a3)} +} + +func (_c *AttemptBuilder_NewBumpAttempt_Call) Run(run func(_a0 context.Context, _a1 logger.Logger, _a2 *types.Transaction, _a3 types.Attempt)) *AttemptBuilder_NewBumpAttempt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(logger.Logger), args[2].(*types.Transaction), args[3].(types.Attempt)) + }) + return _c +} + +func (_c *AttemptBuilder_NewBumpAttempt_Call) Return(_a0 *types.Attempt, _a1 error) *AttemptBuilder_NewBumpAttempt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AttemptBuilder_NewBumpAttempt_Call) RunAndReturn(run func(context.Context, logger.Logger, *types.Transaction, types.Attempt) (*types.Attempt, error)) *AttemptBuilder_NewBumpAttempt_Call { + _c.Call.Return(run) + return _c +} + +// NewAttemptBuilder creates a new instance of AttemptBuilder. 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 NewAttemptBuilder(t interface { + mock.TestingT + Cleanup(func()) +}) *AttemptBuilder { + mock := &AttemptBuilder{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/chains/evm/txm/mocks/client.go b/core/chains/evm/txm/mocks/client.go new file mode 100644 index 00000000000..cac2e55491a --- /dev/null +++ b/core/chains/evm/txm/mocks/client.go @@ -0,0 +1,204 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + big "math/big" + + common "github.com/ethereum/go-ethereum/common" + + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +// Client is an autogenerated mock type for the Client type +type Client struct { + mock.Mock +} + +type Client_Expecter struct { + mock *mock.Mock +} + +func (_m *Client) EXPECT() *Client_Expecter { + return &Client_Expecter{mock: &_m.Mock} +} + +// NonceAt provides a mock function with given fields: _a0, _a1, _a2 +func (_m *Client) NonceAt(_a0 context.Context, _a1 common.Address, _a2 *big.Int) (uint64, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for NonceAt") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) (uint64, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, *big.Int) uint64); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, *big.Int) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_NonceAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NonceAt' +type Client_NonceAt_Call struct { + *mock.Call +} + +// NonceAt is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 common.Address +// - _a2 *big.Int +func (_e *Client_Expecter) NonceAt(_a0 interface{}, _a1 interface{}, _a2 interface{}) *Client_NonceAt_Call { + return &Client_NonceAt_Call{Call: _e.mock.On("NonceAt", _a0, _a1, _a2)} +} + +func (_c *Client_NonceAt_Call) Run(run func(_a0 context.Context, _a1 common.Address, _a2 *big.Int)) *Client_NonceAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(*big.Int)) + }) + return _c +} + +func (_c *Client_NonceAt_Call) Return(_a0 uint64, _a1 error) *Client_NonceAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_NonceAt_Call) RunAndReturn(run func(context.Context, common.Address, *big.Int) (uint64, error)) *Client_NonceAt_Call { + _c.Call.Return(run) + return _c +} + +// PendingNonceAt provides a mock function with given fields: _a0, _a1 +func (_m *Client) PendingNonceAt(_a0 context.Context, _a1 common.Address) (uint64, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for PendingNonceAt") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) (uint64, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address) uint64); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_PendingNonceAt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PendingNonceAt' +type Client_PendingNonceAt_Call struct { + *mock.Call +} + +// PendingNonceAt is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 common.Address +func (_e *Client_Expecter) PendingNonceAt(_a0 interface{}, _a1 interface{}) *Client_PendingNonceAt_Call { + return &Client_PendingNonceAt_Call{Call: _e.mock.On("PendingNonceAt", _a0, _a1)} +} + +func (_c *Client_PendingNonceAt_Call) Run(run func(_a0 context.Context, _a1 common.Address)) *Client_PendingNonceAt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address)) + }) + return _c +} + +func (_c *Client_PendingNonceAt_Call) Return(_a0 uint64, _a1 error) *Client_PendingNonceAt_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_PendingNonceAt_Call) RunAndReturn(run func(context.Context, common.Address) (uint64, error)) *Client_PendingNonceAt_Call { + _c.Call.Return(run) + return _c +} + +// SendTransaction provides a mock function with given fields: ctx, tx, attempt +func (_m *Client) SendTransaction(ctx context.Context, tx *types.Transaction, attempt *types.Attempt) error { + ret := _m.Called(ctx, tx, attempt) + + if len(ret) == 0 { + panic("no return value specified for SendTransaction") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, *types.Attempt) error); ok { + r0 = rf(ctx, tx, attempt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Client_SendTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendTransaction' +type Client_SendTransaction_Call struct { + *mock.Call +} + +// SendTransaction is a helper method to define mock.On call +// - ctx context.Context +// - tx *types.Transaction +// - attempt *types.Attempt +func (_e *Client_Expecter) SendTransaction(ctx interface{}, tx interface{}, attempt interface{}) *Client_SendTransaction_Call { + return &Client_SendTransaction_Call{Call: _e.mock.On("SendTransaction", ctx, tx, attempt)} +} + +func (_c *Client_SendTransaction_Call) Run(run func(ctx context.Context, tx *types.Transaction, attempt *types.Attempt)) *Client_SendTransaction_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*types.Transaction), args[2].(*types.Attempt)) + }) + return _c +} + +func (_c *Client_SendTransaction_Call) Return(_a0 error) *Client_SendTransaction_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Client_SendTransaction_Call) RunAndReturn(run func(context.Context, *types.Transaction, *types.Attempt) error) *Client_SendTransaction_Call { + _c.Call.Return(run) + return _c +} + +// NewClient creates a new instance of Client. 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 NewClient(t interface { + mock.TestingT + Cleanup(func()) +}) *Client { + mock := &Client{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/chains/evm/txm/mocks/keystore.go b/core/chains/evm/txm/mocks/keystore.go new file mode 100644 index 00000000000..3d11a6fa549 --- /dev/null +++ b/core/chains/evm/txm/mocks/keystore.go @@ -0,0 +1,98 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + big "math/big" + + common "github.com/ethereum/go-ethereum/common" + + mock "github.com/stretchr/testify/mock" +) + +// Keystore is an autogenerated mock type for the Keystore type +type Keystore struct { + mock.Mock +} + +type Keystore_Expecter struct { + mock *mock.Mock +} + +func (_m *Keystore) EXPECT() *Keystore_Expecter { + return &Keystore_Expecter{mock: &_m.Mock} +} + +// EnabledAddressesForChain provides a mock function with given fields: ctx, chainID +func (_m *Keystore) EnabledAddressesForChain(ctx context.Context, chainID *big.Int) ([]common.Address, error) { + ret := _m.Called(ctx, chainID) + + if len(ret) == 0 { + panic("no return value specified for EnabledAddressesForChain") + } + + var r0 []common.Address + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) ([]common.Address, error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) []common.Address); ok { + r0 = rf(ctx, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]common.Address) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Keystore_EnabledAddressesForChain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnabledAddressesForChain' +type Keystore_EnabledAddressesForChain_Call struct { + *mock.Call +} + +// EnabledAddressesForChain is a helper method to define mock.On call +// - ctx context.Context +// - chainID *big.Int +func (_e *Keystore_Expecter) EnabledAddressesForChain(ctx interface{}, chainID interface{}) *Keystore_EnabledAddressesForChain_Call { + return &Keystore_EnabledAddressesForChain_Call{Call: _e.mock.On("EnabledAddressesForChain", ctx, chainID)} +} + +func (_c *Keystore_EnabledAddressesForChain_Call) Run(run func(ctx context.Context, chainID *big.Int)) *Keystore_EnabledAddressesForChain_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*big.Int)) + }) + return _c +} + +func (_c *Keystore_EnabledAddressesForChain_Call) Return(addresses []common.Address, err error) *Keystore_EnabledAddressesForChain_Call { + _c.Call.Return(addresses, err) + return _c +} + +func (_c *Keystore_EnabledAddressesForChain_Call) RunAndReturn(run func(context.Context, *big.Int) ([]common.Address, error)) *Keystore_EnabledAddressesForChain_Call { + _c.Call.Return(run) + return _c +} + +// NewKeystore creates a new instance of Keystore. 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 NewKeystore(t interface { + mock.TestingT + Cleanup(func()) +}) *Keystore { + mock := &Keystore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/chains/evm/txm/mocks/tx_store.go b/core/chains/evm/txm/mocks/tx_store.go new file mode 100644 index 00000000000..318b36942b8 --- /dev/null +++ b/core/chains/evm/txm/mocks/tx_store.go @@ -0,0 +1,647 @@ +// Code generated by mockery v2.50.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + common "github.com/ethereum/go-ethereum/common" + + mock "github.com/stretchr/testify/mock" + + types "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +// TxStore is an autogenerated mock type for the TxStore type +type TxStore struct { + mock.Mock +} + +type TxStore_Expecter struct { + mock *mock.Mock +} + +func (_m *TxStore) EXPECT() *TxStore_Expecter { + return &TxStore_Expecter{mock: &_m.Mock} +} + +// AbandonPendingTransactions provides a mock function with given fields: _a0, _a1 +func (_m *TxStore) AbandonPendingTransactions(_a0 context.Context, _a1 common.Address) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for AbandonPendingTransactions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxStore_AbandonPendingTransactions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AbandonPendingTransactions' +type TxStore_AbandonPendingTransactions_Call struct { + *mock.Call +} + +// AbandonPendingTransactions is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 common.Address +func (_e *TxStore_Expecter) AbandonPendingTransactions(_a0 interface{}, _a1 interface{}) *TxStore_AbandonPendingTransactions_Call { + return &TxStore_AbandonPendingTransactions_Call{Call: _e.mock.On("AbandonPendingTransactions", _a0, _a1)} +} + +func (_c *TxStore_AbandonPendingTransactions_Call) Run(run func(_a0 context.Context, _a1 common.Address)) *TxStore_AbandonPendingTransactions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address)) + }) + return _c +} + +func (_c *TxStore_AbandonPendingTransactions_Call) Return(_a0 error) *TxStore_AbandonPendingTransactions_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxStore_AbandonPendingTransactions_Call) RunAndReturn(run func(context.Context, common.Address) error) *TxStore_AbandonPendingTransactions_Call { + _c.Call.Return(run) + return _c +} + +// AppendAttemptToTransaction provides a mock function with given fields: _a0, _a1, _a2, _a3 +func (_m *TxStore) AppendAttemptToTransaction(_a0 context.Context, _a1 uint64, _a2 common.Address, _a3 *types.Attempt) error { + ret := _m.Called(_a0, _a1, _a2, _a3) + + if len(ret) == 0 { + panic("no return value specified for AppendAttemptToTransaction") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, common.Address, *types.Attempt) error); ok { + r0 = rf(_a0, _a1, _a2, _a3) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxStore_AppendAttemptToTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AppendAttemptToTransaction' +type TxStore_AppendAttemptToTransaction_Call struct { + *mock.Call +} + +// AppendAttemptToTransaction is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 uint64 +// - _a2 common.Address +// - _a3 *types.Attempt +func (_e *TxStore_Expecter) AppendAttemptToTransaction(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}) *TxStore_AppendAttemptToTransaction_Call { + return &TxStore_AppendAttemptToTransaction_Call{Call: _e.mock.On("AppendAttemptToTransaction", _a0, _a1, _a2, _a3)} +} + +func (_c *TxStore_AppendAttemptToTransaction_Call) Run(run func(_a0 context.Context, _a1 uint64, _a2 common.Address, _a3 *types.Attempt)) *TxStore_AppendAttemptToTransaction_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(common.Address), args[3].(*types.Attempt)) + }) + return _c +} + +func (_c *TxStore_AppendAttemptToTransaction_Call) Return(_a0 error) *TxStore_AppendAttemptToTransaction_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxStore_AppendAttemptToTransaction_Call) RunAndReturn(run func(context.Context, uint64, common.Address, *types.Attempt) error) *TxStore_AppendAttemptToTransaction_Call { + _c.Call.Return(run) + return _c +} + +// CreateEmptyUnconfirmedTransaction provides a mock function with given fields: _a0, _a1, _a2, _a3 +func (_m *TxStore) CreateEmptyUnconfirmedTransaction(_a0 context.Context, _a1 common.Address, _a2 uint64, _a3 uint64) (*types.Transaction, error) { + ret := _m.Called(_a0, _a1, _a2, _a3) + + if len(ret) == 0 { + panic("no return value specified for CreateEmptyUnconfirmedTransaction") + } + + var r0 *types.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, uint64, uint64) (*types.Transaction, error)); ok { + return rf(_a0, _a1, _a2, _a3) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, uint64, uint64) *types.Transaction); ok { + r0 = rf(_a0, _a1, _a2, _a3) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, uint64, uint64) error); ok { + r1 = rf(_a0, _a1, _a2, _a3) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TxStore_CreateEmptyUnconfirmedTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateEmptyUnconfirmedTransaction' +type TxStore_CreateEmptyUnconfirmedTransaction_Call struct { + *mock.Call +} + +// CreateEmptyUnconfirmedTransaction is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 common.Address +// - _a2 uint64 +// - _a3 uint64 +func (_e *TxStore_Expecter) CreateEmptyUnconfirmedTransaction(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}) *TxStore_CreateEmptyUnconfirmedTransaction_Call { + return &TxStore_CreateEmptyUnconfirmedTransaction_Call{Call: _e.mock.On("CreateEmptyUnconfirmedTransaction", _a0, _a1, _a2, _a3)} +} + +func (_c *TxStore_CreateEmptyUnconfirmedTransaction_Call) Run(run func(_a0 context.Context, _a1 common.Address, _a2 uint64, _a3 uint64)) *TxStore_CreateEmptyUnconfirmedTransaction_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(uint64), args[3].(uint64)) + }) + return _c +} + +func (_c *TxStore_CreateEmptyUnconfirmedTransaction_Call) Return(_a0 *types.Transaction, _a1 error) *TxStore_CreateEmptyUnconfirmedTransaction_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *TxStore_CreateEmptyUnconfirmedTransaction_Call) RunAndReturn(run func(context.Context, common.Address, uint64, uint64) (*types.Transaction, error)) *TxStore_CreateEmptyUnconfirmedTransaction_Call { + _c.Call.Return(run) + return _c +} + +// CreateTransaction provides a mock function with given fields: _a0, _a1 +func (_m *TxStore) CreateTransaction(_a0 context.Context, _a1 *types.TxRequest) (*types.Transaction, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for CreateTransaction") + } + + var r0 *types.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *types.TxRequest) (*types.Transaction, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *types.TxRequest) *types.Transaction); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *types.TxRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TxStore_CreateTransaction_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTransaction' +type TxStore_CreateTransaction_Call struct { + *mock.Call +} + +// CreateTransaction is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *types.TxRequest +func (_e *TxStore_Expecter) CreateTransaction(_a0 interface{}, _a1 interface{}) *TxStore_CreateTransaction_Call { + return &TxStore_CreateTransaction_Call{Call: _e.mock.On("CreateTransaction", _a0, _a1)} +} + +func (_c *TxStore_CreateTransaction_Call) Run(run func(_a0 context.Context, _a1 *types.TxRequest)) *TxStore_CreateTransaction_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*types.TxRequest)) + }) + return _c +} + +func (_c *TxStore_CreateTransaction_Call) Return(_a0 *types.Transaction, _a1 error) *TxStore_CreateTransaction_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *TxStore_CreateTransaction_Call) RunAndReturn(run func(context.Context, *types.TxRequest) (*types.Transaction, error)) *TxStore_CreateTransaction_Call { + _c.Call.Return(run) + return _c +} + +// DeleteAttemptForUnconfirmedTx provides a mock function with given fields: _a0, _a1, _a2, _a3 +func (_m *TxStore) DeleteAttemptForUnconfirmedTx(_a0 context.Context, _a1 uint64, _a2 *types.Attempt, _a3 common.Address) error { + ret := _m.Called(_a0, _a1, _a2, _a3) + + if len(ret) == 0 { + panic("no return value specified for DeleteAttemptForUnconfirmedTx") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, *types.Attempt, common.Address) error); ok { + r0 = rf(_a0, _a1, _a2, _a3) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxStore_DeleteAttemptForUnconfirmedTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteAttemptForUnconfirmedTx' +type TxStore_DeleteAttemptForUnconfirmedTx_Call struct { + *mock.Call +} + +// DeleteAttemptForUnconfirmedTx is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 uint64 +// - _a2 *types.Attempt +// - _a3 common.Address +func (_e *TxStore_Expecter) DeleteAttemptForUnconfirmedTx(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}) *TxStore_DeleteAttemptForUnconfirmedTx_Call { + return &TxStore_DeleteAttemptForUnconfirmedTx_Call{Call: _e.mock.On("DeleteAttemptForUnconfirmedTx", _a0, _a1, _a2, _a3)} +} + +func (_c *TxStore_DeleteAttemptForUnconfirmedTx_Call) Run(run func(_a0 context.Context, _a1 uint64, _a2 *types.Attempt, _a3 common.Address)) *TxStore_DeleteAttemptForUnconfirmedTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(*types.Attempt), args[3].(common.Address)) + }) + return _c +} + +func (_c *TxStore_DeleteAttemptForUnconfirmedTx_Call) Return(_a0 error) *TxStore_DeleteAttemptForUnconfirmedTx_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxStore_DeleteAttemptForUnconfirmedTx_Call) RunAndReturn(run func(context.Context, uint64, *types.Attempt, common.Address) error) *TxStore_DeleteAttemptForUnconfirmedTx_Call { + _c.Call.Return(run) + return _c +} + +// FetchUnconfirmedTransactionAtNonceWithCount provides a mock function with given fields: _a0, _a1, _a2 +func (_m *TxStore) FetchUnconfirmedTransactionAtNonceWithCount(_a0 context.Context, _a1 uint64, _a2 common.Address) (*types.Transaction, int, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for FetchUnconfirmedTransactionAtNonceWithCount") + } + + var r0 *types.Transaction + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, common.Address) (*types.Transaction, int, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, common.Address) *types.Transaction); ok { + r0 = rf(_a0, _a1, _a2) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, common.Address) int); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64, common.Address) error); ok { + r2 = rf(_a0, _a1, _a2) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FetchUnconfirmedTransactionAtNonceWithCount' +type TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call struct { + *mock.Call +} + +// FetchUnconfirmedTransactionAtNonceWithCount is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 uint64 +// - _a2 common.Address +func (_e *TxStore_Expecter) FetchUnconfirmedTransactionAtNonceWithCount(_a0 interface{}, _a1 interface{}, _a2 interface{}) *TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call { + return &TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call{Call: _e.mock.On("FetchUnconfirmedTransactionAtNonceWithCount", _a0, _a1, _a2)} +} + +func (_c *TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call) Run(run func(_a0 context.Context, _a1 uint64, _a2 common.Address)) *TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(common.Address)) + }) + return _c +} + +func (_c *TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call) Return(_a0 *types.Transaction, _a1 int, _a2 error) *TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call) RunAndReturn(run func(context.Context, uint64, common.Address) (*types.Transaction, int, error)) *TxStore_FetchUnconfirmedTransactionAtNonceWithCount_Call { + _c.Call.Return(run) + return _c +} + +// MarkConfirmedAndReorgedTransactions provides a mock function with given fields: _a0, _a1, _a2 +func (_m *TxStore) MarkConfirmedAndReorgedTransactions(_a0 context.Context, _a1 uint64, _a2 common.Address) ([]*types.Transaction, []uint64, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for MarkConfirmedAndReorgedTransactions") + } + + var r0 []*types.Transaction + var r1 []uint64 + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, common.Address) ([]*types.Transaction, []uint64, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, common.Address) []*types.Transaction); ok { + r0 = rf(_a0, _a1, _a2) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, common.Address) []uint64); ok { + r1 = rf(_a0, _a1, _a2) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]uint64) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64, common.Address) error); ok { + r2 = rf(_a0, _a1, _a2) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// TxStore_MarkConfirmedAndReorgedTransactions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkConfirmedAndReorgedTransactions' +type TxStore_MarkConfirmedAndReorgedTransactions_Call struct { + *mock.Call +} + +// MarkConfirmedAndReorgedTransactions is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 uint64 +// - _a2 common.Address +func (_e *TxStore_Expecter) MarkConfirmedAndReorgedTransactions(_a0 interface{}, _a1 interface{}, _a2 interface{}) *TxStore_MarkConfirmedAndReorgedTransactions_Call { + return &TxStore_MarkConfirmedAndReorgedTransactions_Call{Call: _e.mock.On("MarkConfirmedAndReorgedTransactions", _a0, _a1, _a2)} +} + +func (_c *TxStore_MarkConfirmedAndReorgedTransactions_Call) Run(run func(_a0 context.Context, _a1 uint64, _a2 common.Address)) *TxStore_MarkConfirmedAndReorgedTransactions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(common.Address)) + }) + return _c +} + +func (_c *TxStore_MarkConfirmedAndReorgedTransactions_Call) Return(_a0 []*types.Transaction, _a1 []uint64, _a2 error) *TxStore_MarkConfirmedAndReorgedTransactions_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *TxStore_MarkConfirmedAndReorgedTransactions_Call) RunAndReturn(run func(context.Context, uint64, common.Address) ([]*types.Transaction, []uint64, error)) *TxStore_MarkConfirmedAndReorgedTransactions_Call { + _c.Call.Return(run) + return _c +} + +// MarkTxFatal provides a mock function with given fields: _a0, _a1, _a2 +func (_m *TxStore) MarkTxFatal(_a0 context.Context, _a1 *types.Transaction, _a2 common.Address) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for MarkTxFatal") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *types.Transaction, common.Address) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxStore_MarkTxFatal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkTxFatal' +type TxStore_MarkTxFatal_Call struct { + *mock.Call +} + +// MarkTxFatal is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *types.Transaction +// - _a2 common.Address +func (_e *TxStore_Expecter) MarkTxFatal(_a0 interface{}, _a1 interface{}, _a2 interface{}) *TxStore_MarkTxFatal_Call { + return &TxStore_MarkTxFatal_Call{Call: _e.mock.On("MarkTxFatal", _a0, _a1, _a2)} +} + +func (_c *TxStore_MarkTxFatal_Call) Run(run func(_a0 context.Context, _a1 *types.Transaction, _a2 common.Address)) *TxStore_MarkTxFatal_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*types.Transaction), args[2].(common.Address)) + }) + return _c +} + +func (_c *TxStore_MarkTxFatal_Call) Return(_a0 error) *TxStore_MarkTxFatal_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxStore_MarkTxFatal_Call) RunAndReturn(run func(context.Context, *types.Transaction, common.Address) error) *TxStore_MarkTxFatal_Call { + _c.Call.Return(run) + return _c +} + +// MarkUnconfirmedTransactionPurgeable provides a mock function with given fields: _a0, _a1, _a2 +func (_m *TxStore) MarkUnconfirmedTransactionPurgeable(_a0 context.Context, _a1 uint64, _a2 common.Address) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for MarkUnconfirmedTransactionPurgeable") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, common.Address) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxStore_MarkUnconfirmedTransactionPurgeable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkUnconfirmedTransactionPurgeable' +type TxStore_MarkUnconfirmedTransactionPurgeable_Call struct { + *mock.Call +} + +// MarkUnconfirmedTransactionPurgeable is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 uint64 +// - _a2 common.Address +func (_e *TxStore_Expecter) MarkUnconfirmedTransactionPurgeable(_a0 interface{}, _a1 interface{}, _a2 interface{}) *TxStore_MarkUnconfirmedTransactionPurgeable_Call { + return &TxStore_MarkUnconfirmedTransactionPurgeable_Call{Call: _e.mock.On("MarkUnconfirmedTransactionPurgeable", _a0, _a1, _a2)} +} + +func (_c *TxStore_MarkUnconfirmedTransactionPurgeable_Call) Run(run func(_a0 context.Context, _a1 uint64, _a2 common.Address)) *TxStore_MarkUnconfirmedTransactionPurgeable_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(common.Address)) + }) + return _c +} + +func (_c *TxStore_MarkUnconfirmedTransactionPurgeable_Call) Return(_a0 error) *TxStore_MarkUnconfirmedTransactionPurgeable_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxStore_MarkUnconfirmedTransactionPurgeable_Call) RunAndReturn(run func(context.Context, uint64, common.Address) error) *TxStore_MarkUnconfirmedTransactionPurgeable_Call { + _c.Call.Return(run) + return _c +} + +// UpdateTransactionBroadcast provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4 +func (_m *TxStore) UpdateTransactionBroadcast(_a0 context.Context, _a1 uint64, _a2 uint64, _a3 common.Hash, _a4 common.Address) error { + ret := _m.Called(_a0, _a1, _a2, _a3, _a4) + + if len(ret) == 0 { + panic("no return value specified for UpdateTransactionBroadcast") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64, common.Hash, common.Address) error); ok { + r0 = rf(_a0, _a1, _a2, _a3, _a4) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// TxStore_UpdateTransactionBroadcast_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateTransactionBroadcast' +type TxStore_UpdateTransactionBroadcast_Call struct { + *mock.Call +} + +// UpdateTransactionBroadcast is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 uint64 +// - _a2 uint64 +// - _a3 common.Hash +// - _a4 common.Address +func (_e *TxStore_Expecter) UpdateTransactionBroadcast(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}, _a4 interface{}) *TxStore_UpdateTransactionBroadcast_Call { + return &TxStore_UpdateTransactionBroadcast_Call{Call: _e.mock.On("UpdateTransactionBroadcast", _a0, _a1, _a2, _a3, _a4)} +} + +func (_c *TxStore_UpdateTransactionBroadcast_Call) Run(run func(_a0 context.Context, _a1 uint64, _a2 uint64, _a3 common.Hash, _a4 common.Address)) *TxStore_UpdateTransactionBroadcast_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64), args[2].(uint64), args[3].(common.Hash), args[4].(common.Address)) + }) + return _c +} + +func (_c *TxStore_UpdateTransactionBroadcast_Call) Return(_a0 error) *TxStore_UpdateTransactionBroadcast_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *TxStore_UpdateTransactionBroadcast_Call) RunAndReturn(run func(context.Context, uint64, uint64, common.Hash, common.Address) error) *TxStore_UpdateTransactionBroadcast_Call { + _c.Call.Return(run) + return _c +} + +// UpdateUnstartedTransactionWithNonce provides a mock function with given fields: _a0, _a1, _a2 +func (_m *TxStore) UpdateUnstartedTransactionWithNonce(_a0 context.Context, _a1 common.Address, _a2 uint64) (*types.Transaction, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for UpdateUnstartedTransactionWithNonce") + } + + var r0 *types.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, uint64) (*types.Transaction, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, common.Address, uint64) *types.Transaction); ok { + r0 = rf(_a0, _a1, _a2) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, common.Address, uint64) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// TxStore_UpdateUnstartedTransactionWithNonce_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateUnstartedTransactionWithNonce' +type TxStore_UpdateUnstartedTransactionWithNonce_Call struct { + *mock.Call +} + +// UpdateUnstartedTransactionWithNonce is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 common.Address +// - _a2 uint64 +func (_e *TxStore_Expecter) UpdateUnstartedTransactionWithNonce(_a0 interface{}, _a1 interface{}, _a2 interface{}) *TxStore_UpdateUnstartedTransactionWithNonce_Call { + return &TxStore_UpdateUnstartedTransactionWithNonce_Call{Call: _e.mock.On("UpdateUnstartedTransactionWithNonce", _a0, _a1, _a2)} +} + +func (_c *TxStore_UpdateUnstartedTransactionWithNonce_Call) Run(run func(_a0 context.Context, _a1 common.Address, _a2 uint64)) *TxStore_UpdateUnstartedTransactionWithNonce_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(common.Address), args[2].(uint64)) + }) + return _c +} + +func (_c *TxStore_UpdateUnstartedTransactionWithNonce_Call) Return(_a0 *types.Transaction, _a1 error) *TxStore_UpdateUnstartedTransactionWithNonce_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *TxStore_UpdateUnstartedTransactionWithNonce_Call) RunAndReturn(run func(context.Context, common.Address, uint64) (*types.Transaction, error)) *TxStore_UpdateUnstartedTransactionWithNonce_Call { + _c.Call.Return(run) + return _c +} + +// NewTxStore creates a new instance of TxStore. 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 NewTxStore(t interface { + mock.TestingT + Cleanup(func()) +}) *TxStore { + mock := &TxStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/chains/evm/txm/orchestrator.go b/core/chains/evm/txm/orchestrator.go new file mode 100644 index 00000000000..ae981e153b0 --- /dev/null +++ b/core/chains/evm/txm/orchestrator.go @@ -0,0 +1,363 @@ +package txm + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/google/uuid" + nullv4 "gopkg.in/guregu/null.v4" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils" + + "github.com/smartcontractkit/chainlink/v2/common/txmgr" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + "github.com/smartcontractkit/chainlink/v2/common/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/forwarders" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + txmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" +) + +type OrchestratorTxStore interface { + Add(addresses ...common.Address) error + FetchUnconfirmedTransactionAtNonceWithCount(context.Context, uint64, common.Address) (*txmtypes.Transaction, int, error) + FindTxWithIdempotencyKey(context.Context, string) (*txmtypes.Transaction, error) +} + +type OrchestratorKeystore interface { + CheckEnabled(ctx context.Context, address common.Address, chainID *big.Int) error + EnabledAddressesForChain(ctx context.Context, chainID *big.Int) (addresses []common.Address, err error) +} + +type OrchestratorAttemptBuilder[ + BLOCK_HASH types.Hashable, + HEAD types.Head[BLOCK_HASH], +] interface { + services.Service + OnNewLongestChain(ctx context.Context, head HEAD) +} + +// Generics are necessary to keep TXMv2 backwards compatible +type Orchestrator[ + BLOCK_HASH types.Hashable, + HEAD types.Head[BLOCK_HASH], +] struct { + services.StateMachine + lggr logger.SugaredLogger + chainID *big.Int + txm *Txm + txStore OrchestratorTxStore + fwdMgr *forwarders.FwdMgr + keystore OrchestratorKeystore + attemptBuilder OrchestratorAttemptBuilder[BLOCK_HASH, HEAD] + resumeCallback txmgr.ResumeCallback +} + +func NewTxmOrchestrator[BLOCK_HASH types.Hashable, HEAD types.Head[BLOCK_HASH]]( + lggr logger.Logger, + chainID *big.Int, + txm *Txm, + txStore OrchestratorTxStore, + fwdMgr *forwarders.FwdMgr, + keystore OrchestratorKeystore, + attemptBuilder OrchestratorAttemptBuilder[BLOCK_HASH, HEAD], +) *Orchestrator[BLOCK_HASH, HEAD] { + return &Orchestrator[BLOCK_HASH, HEAD]{ + lggr: logger.Sugared(logger.Named(lggr, "Orchestrator")), + chainID: chainID, + txm: txm, + txStore: txStore, + keystore: keystore, + attemptBuilder: attemptBuilder, + fwdMgr: fwdMgr, + } +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) Start(ctx context.Context) error { + return o.StartOnce("Orchestrator", func() error { + var ms services.MultiStart + if err := ms.Start(ctx, o.attemptBuilder); err != nil { + return fmt.Errorf("Orchestrator: AttemptBuilder failed to start: %w", err) + } + addresses, err := o.keystore.EnabledAddressesForChain(ctx, o.chainID) + if err != nil { + return err + } + for _, address := range addresses { + err := o.txStore.Add(address) + if err != nil { + return err + } + } + if err := ms.Start(ctx, o.txm); err != nil { + return fmt.Errorf("Orchestrator: Txm failed to start: %w", err) + } + if o.fwdMgr != nil { + if err := ms.Start(ctx, o.fwdMgr); err != nil { + return fmt.Errorf("Orchestrator: ForwarderManager failed to start: %w", err) + } + } + return nil + }) +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) Close() (merr error) { + return o.StopOnce("Orchestrator", func() error { + if o.fwdMgr != nil { + if err := o.fwdMgr.Close(); err != nil { + merr = errors.Join(merr, fmt.Errorf("Orchestrator failed to stop ForwarderManager: %w", err)) + } + } + if err := o.txm.Close(); err != nil { + merr = errors.Join(merr, fmt.Errorf("Orchestrator failed to stop Txm: %w", err)) + } + if err := o.attemptBuilder.Close(); err != nil { + merr = errors.Join(merr, fmt.Errorf("Orchestrator failed to stop AttemptBuilder: %w", err)) + } + return merr + }) +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) Trigger(addr common.Address) { + o.txm.Trigger(addr) +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) Name() string { + return o.lggr.Name() +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) HealthReport() map[string]error { + return map[string]error{o.Name(): o.Healthy()} +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) RegisterResumeCallback(fn txmgr.ResumeCallback) { + o.resumeCallback = fn +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) Reset(addr common.Address, abandon bool) error { + ok := o.IfStarted(func() { + if err := o.txm.Abandon(addr); err != nil { + o.lggr.Error(err) + } + }) + if !ok { + return errors.New("Orchestrator not started yet") + } + return nil +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) OnNewLongestChain(ctx context.Context, head HEAD) { + ok := o.IfStarted(func() { + o.attemptBuilder.OnNewLongestChain(ctx, head) + }) + if !ok { + o.lggr.Debugw("Not started; ignoring head", "head", head, "state", o.State()) + } +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) CreateTransaction(ctx context.Context, request txmgrtypes.TxRequest[common.Address, common.Hash]) (tx txmgrtypes.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], err error) { + var wrappedTx *txmtypes.Transaction + if request.IdempotencyKey != nil { + wrappedTx, err = o.txStore.FindTxWithIdempotencyKey(ctx, *request.IdempotencyKey) + if err != nil { + return + } + } + + if wrappedTx != nil { + o.lggr.Infof("Found Tx with IdempotencyKey: %v. Returning existing Tx without creating a new one.", *wrappedTx.IdempotencyKey) + } else { + if kErr := o.keystore.CheckEnabled(ctx, request.FromAddress, o.chainID); kErr != nil { + return tx, fmt.Errorf("cannot send transaction from %s on chain ID %s: %w", request.FromAddress, o.chainID.String(), kErr) + } + + var pipelineTaskRunID uuid.NullUUID + if request.PipelineTaskRunID != nil { + pipelineTaskRunID.UUID = *request.PipelineTaskRunID + pipelineTaskRunID.Valid = true + } + + if o.fwdMgr != nil && (!utils.IsZero(request.ForwarderAddress)) { + fwdPayload, fwdErr := o.fwdMgr.ConvertPayload(request.ToAddress, request.EncodedPayload) + if fwdErr == nil { + // Handling meta not set at caller. + if request.Meta != nil { + request.Meta.FwdrDestAddress = &request.ToAddress + } else { + request.Meta = &txmgrtypes.TxMeta[common.Address, common.Hash]{ + FwdrDestAddress: &request.ToAddress, + } + } + request.ToAddress = request.ForwarderAddress + request.EncodedPayload = fwdPayload + } else { + o.lggr.Errorf("Failed to use forwarder set upstream: %v", fwdErr.Error()) + } + } + + var meta *sqlutil.JSON + if request.Meta != nil { + raw, mErr := json.Marshal(request.Meta) + if mErr != nil { + return tx, mErr + } + m := sqlutil.JSON(raw) + meta = &m + } + + wrappedTxRequest := &txmtypes.TxRequest{ + IdempotencyKey: request.IdempotencyKey, + ChainID: o.chainID, + FromAddress: request.FromAddress, + ToAddress: request.ToAddress, + Value: &request.Value, + Data: request.EncodedPayload, + SpecifiedGasLimit: request.FeeLimit, + Meta: meta, + ForwarderAddress: request.ForwarderAddress, + + PipelineTaskRunID: pipelineTaskRunID, + MinConfirmations: request.MinConfirmations, + SignalCallback: request.SignalCallback, + } + + wrappedTx, err = o.txm.CreateTransaction(ctx, wrappedTxRequest) + if err != nil { + return + } + o.txm.Trigger(request.FromAddress) + } + + if wrappedTx.ID > math.MaxInt64 { + return tx, fmt.Errorf("overflow for int64: %d", wrappedTx.ID) + } + + tx = txmgrtypes.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]{ + ID: int64(wrappedTx.ID), + IdempotencyKey: wrappedTx.IdempotencyKey, + FromAddress: wrappedTx.FromAddress, + ToAddress: wrappedTx.ToAddress, + EncodedPayload: wrappedTx.Data, + Value: *wrappedTx.Value, + FeeLimit: wrappedTx.SpecifiedGasLimit, + CreatedAt: wrappedTx.CreatedAt, + Meta: wrappedTx.Meta, + Subject: wrappedTx.Subject, + ChainID: wrappedTx.ChainID, + + PipelineTaskRunID: wrappedTx.PipelineTaskRunID, + MinConfirmations: wrappedTx.MinConfirmations, + SignalCallback: wrappedTx.SignalCallback, + CallbackCompleted: wrappedTx.CallbackCompleted, + } + return +} + +// CountTransactionsByState was required for backwards compatibility and it's used only for unconfirmed transactions. +func (o *Orchestrator[BLOCK_HASH, HEAD]) CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState) (uint32, error) { + addresses, err := o.keystore.EnabledAddressesForChain(ctx, o.chainID) + if err != nil { + return 0, err + } + total := 0 + for _, address := range addresses { + _, count, err := o.txStore.FetchUnconfirmedTransactionAtNonceWithCount(ctx, 0, address) + if err != nil { + return 0, err + } + total += count + } + + //nolint:gosec // disable G115 + return uint32(total), nil +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) FindEarliestUnconfirmedBroadcastTime(ctx context.Context) (time nullv4.Time, err error) { + return +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context) (time nullv4.Int, err error) { + return +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) FindTxesByMetaFieldAndStates(ctx context.Context, metaField string, metaValue string, states []txmgrtypes.TxState, chainID *big.Int) (txs []*txmgrtypes.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], err error) { + return +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) (txs []*txmgrtypes.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], err error) { + return +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) (txs []*txmgrtypes.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], err error) { + return +} + +//nolint:revive // keep API backwards compatible +func (o *Orchestrator[BLOCK_HASH, HEAD]) FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []int64, states []txmgrtypes.TxState, chainID *big.Int) (txs []*txmgrtypes.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], err error) { + return +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) GetForwarderForEOA(ctx context.Context, eoa common.Address) (forwarder common.Address, err error) { + if o.fwdMgr != nil { + forwarder, err = o.fwdMgr.ForwarderFor(ctx, eoa) + } + return +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) GetForwarderForEOAOCR2Feeds(ctx context.Context, eoa, ocr2AggregatorID common.Address) (forwarder common.Address, err error) { + if o.fwdMgr != nil { + forwarder, err = o.fwdMgr.ForwarderForOCR2Feeds(ctx, eoa, ocr2AggregatorID) + } + return +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) GetTransactionStatus(ctx context.Context, transactionID string) (status commontypes.TransactionStatus, err error) { + // Loads attempts and receipts in the transaction + tx, err := o.txStore.FindTxWithIdempotencyKey(ctx, transactionID) + if err != nil || tx == nil { + return status, fmt.Errorf("failed to find transaction with IdempotencyKey %s: %w", transactionID, err) + } + + switch tx.State { + case txmgr.TxUnconfirmed: + return commontypes.Pending, nil + case txmgr.TxConfirmed: + // Return unconfirmed for confirmed transactions because they are not yet finalized + return commontypes.Unconfirmed, nil + case txmgr.TxFinalized: + return commontypes.Finalized, nil + case txmgr.TxFatalError: + return commontypes.Fatal, nil + default: + return commontypes.Unknown, nil + } +} + +func (o *Orchestrator[BLOCK_HASH, HEAD]) SendNativeToken(ctx context.Context, chainID *big.Int, from, to common.Address, value big.Int, gasLimit uint64) (tx txmgrtypes.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], err error) { + txRequest := txmgrtypes.TxRequest[common.Address, common.Hash]{ + FromAddress: from, + ToAddress: to, + EncodedPayload: []byte{}, + Value: value, + FeeLimit: gasLimit, + //Strategy: NewSendEveryStrategy(), + } + + tx, err = o.CreateTransaction(ctx, txRequest) + if err != nil { + return + } + + // Trigger the Txm to check for new transaction + o.txm.Trigger(from) + return tx, err +} diff --git a/core/chains/evm/txm/storage/inmemory_store.go b/core/chains/evm/txm/storage/inmemory_store.go new file mode 100644 index 00000000000..57217913d76 --- /dev/null +++ b/core/chains/evm/txm/storage/inmemory_store.go @@ -0,0 +1,358 @@ +package storage + +import ( + "errors" + "fmt" + "math/big" + "sort" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + txmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +const ( + // maxQueuedTransactions is the max limit of UnstartedTransactions and ConfirmedTransactions structures. + maxQueuedTransactions = 250 + // pruneSubset controls the subset of confirmed transactions to prune when the structure reaches its max limit. + // i.e. if the value is 3 and the limit is 90, 30 transactions will be pruned. + pruneSubset = 3 +) + +type InMemoryStore struct { + sync.RWMutex + lggr logger.Logger + txIDCount uint64 + address common.Address + chainID *big.Int + + UnstartedTransactions []*types.Transaction + UnconfirmedTransactions map[uint64]*types.Transaction + ConfirmedTransactions map[uint64]*types.Transaction + FatalTransactions []*types.Transaction + + Transactions map[uint64]*types.Transaction +} + +func NewInMemoryStore(lggr logger.Logger, address common.Address, chainID *big.Int) *InMemoryStore { + return &InMemoryStore{ + lggr: logger.Named(lggr, "InMemoryStore"), + address: address, + chainID: chainID, + UnstartedTransactions: make([]*types.Transaction, 0, maxQueuedTransactions), + UnconfirmedTransactions: make(map[uint64]*types.Transaction), + ConfirmedTransactions: make(map[uint64]*types.Transaction, maxQueuedTransactions), + Transactions: make(map[uint64]*types.Transaction), + } +} + +func (m *InMemoryStore) AbandonPendingTransactions() { + // TODO: append existing fatal transactions and cap the size + m.Lock() + defer m.Unlock() + + for _, tx := range m.UnstartedTransactions { + tx.State = txmgr.TxFatalError + } + for _, tx := range m.FatalTransactions { + delete(m.Transactions, tx.ID) + } + m.FatalTransactions = m.UnstartedTransactions + m.UnstartedTransactions = []*types.Transaction{} + + for _, tx := range m.UnconfirmedTransactions { + tx.State = txmgr.TxFatalError + m.FatalTransactions = append(m.FatalTransactions, tx) + } + m.UnconfirmedTransactions = make(map[uint64]*types.Transaction) +} + +func (m *InMemoryStore) AppendAttemptToTransaction(txNonce uint64, attempt *types.Attempt) error { + m.Lock() + defer m.Unlock() + + tx, exists := m.UnconfirmedTransactions[txNonce] + if !exists { + return fmt.Errorf("unconfirmed tx was not found for nonce: %d - txID: %v", txNonce, attempt.TxID) + } + + if tx.ID != attempt.TxID { + return fmt.Errorf("unconfirmed tx with nonce exists but attempt points to a different txID. Found Tx: %v - txID: %v", m.UnconfirmedTransactions[txNonce], attempt.TxID) + } + + attempt.CreatedAt = time.Now() + attempt.ID = uint64(len(tx.Attempts)) // Attempts are not collectively tracked by the in-memory store so attemptIDs are not unique between transactions and can be reused. + tx.AttemptCount++ + m.UnconfirmedTransactions[txNonce].Attempts = append(m.UnconfirmedTransactions[txNonce].Attempts, attempt.DeepCopy()) + + return nil +} + +func (m *InMemoryStore) CountUnstartedTransactions() int { + m.RLock() + defer m.RUnlock() + + return len(m.UnstartedTransactions) +} + +func (m *InMemoryStore) CreateEmptyUnconfirmedTransaction(nonce uint64, gasLimit uint64) (*types.Transaction, error) { + m.Lock() + defer m.Unlock() + + emptyTx := &types.Transaction{ + ID: m.txIDCount, + ChainID: m.chainID, + Nonce: &nonce, + FromAddress: m.address, + ToAddress: common.Address{}, + Value: big.NewInt(0), + SpecifiedGasLimit: gasLimit, + CreatedAt: time.Now(), + State: txmgr.TxUnconfirmed, + } + + if _, exists := m.UnconfirmedTransactions[nonce]; exists { + return nil, fmt.Errorf("an unconfirmed tx with the same nonce already exists: %v", m.UnconfirmedTransactions[nonce]) + } + + if _, exists := m.ConfirmedTransactions[nonce]; exists { + return nil, fmt.Errorf("a confirmed tx with the same nonce already exists: %v", m.ConfirmedTransactions[nonce]) + } + + m.txIDCount++ + m.UnconfirmedTransactions[nonce] = emptyTx + m.Transactions[emptyTx.ID] = emptyTx + + return emptyTx.DeepCopy(), nil +} + +func (m *InMemoryStore) CreateTransaction(txRequest *types.TxRequest) *types.Transaction { + m.Lock() + defer m.Unlock() + + tx := &types.Transaction{ + ID: m.txIDCount, + IdempotencyKey: txRequest.IdempotencyKey, + ChainID: m.chainID, + FromAddress: m.address, + ToAddress: txRequest.ToAddress, + Value: txRequest.Value, + Data: txRequest.Data, + SpecifiedGasLimit: txRequest.SpecifiedGasLimit, + CreatedAt: time.Now(), + State: txmgr.TxUnstarted, + Meta: txRequest.Meta, + MinConfirmations: txRequest.MinConfirmations, + PipelineTaskRunID: txRequest.PipelineTaskRunID, + SignalCallback: txRequest.SignalCallback, + } + + uLen := len(m.UnstartedTransactions) + if uLen >= maxQueuedTransactions { + m.lggr.Warnw(fmt.Sprintf("Unstarted transactions queue for address: %v reached max limit of: %d. Dropping oldest transactions", m.address, maxQueuedTransactions), + "txs", m.UnstartedTransactions[0:uLen-maxQueuedTransactions+1]) // need to make room for the new tx + for _, tx := range m.UnstartedTransactions[0 : uLen-maxQueuedTransactions+1] { + delete(m.Transactions, tx.ID) + } + m.UnstartedTransactions = m.UnstartedTransactions[uLen-maxQueuedTransactions+1:] + } + + m.txIDCount++ + txCopy := tx.DeepCopy() + m.Transactions[txCopy.ID] = txCopy + m.UnstartedTransactions = append(m.UnstartedTransactions, txCopy) + return tx +} + +func (m *InMemoryStore) FetchUnconfirmedTransactionAtNonceWithCount(latestNonce uint64) (txCopy *types.Transaction, unconfirmedCount int) { + m.RLock() + defer m.RUnlock() + + tx := m.UnconfirmedTransactions[latestNonce] + if tx != nil { + txCopy = tx.DeepCopy() + } + unconfirmedCount = len(m.UnconfirmedTransactions) + return +} + +func (m *InMemoryStore) MarkConfirmedAndReorgedTransactions(latestNonce uint64) ([]*types.Transaction, []uint64, error) { + m.Lock() + defer m.Unlock() + + var confirmedTransactions []*types.Transaction + for _, tx := range m.UnconfirmedTransactions { + if tx.Nonce == nil { + return nil, nil, fmt.Errorf("nonce for txID: %v is empty", tx.ID) + } + existingTx, exists := m.ConfirmedTransactions[*tx.Nonce] + if exists { + m.lggr.Errorw("Another confirmed transaction with the same nonce exists. Transaction will be overwritten.", + "existingTx", existingTx, "newTx", tx) + } + if *tx.Nonce < latestNonce { + tx.State = txmgr.TxConfirmed + confirmedTransactions = append(confirmedTransactions, tx.DeepCopy()) + m.ConfirmedTransactions[*tx.Nonce] = tx + delete(m.UnconfirmedTransactions, *tx.Nonce) + } + } + + var unconfirmedTransactionIDs []uint64 + for _, tx := range m.ConfirmedTransactions { + if tx.Nonce == nil { + return nil, nil, fmt.Errorf("nonce for txID: %v is empty", tx.ID) + } + existingTx, exists := m.UnconfirmedTransactions[*tx.Nonce] + if exists { + m.lggr.Errorw("Another unconfirmed transaction with the same nonce exists. Transaction will overwritten.", + "existingTx", existingTx, "newTx", tx) + } + if *tx.Nonce >= latestNonce { + tx.State = txmgr.TxUnconfirmed + tx.LastBroadcastAt = nil // Mark reorged transaction as if it wasn't broadcasted before + unconfirmedTransactionIDs = append(unconfirmedTransactionIDs, tx.ID) + m.UnconfirmedTransactions[*tx.Nonce] = tx + delete(m.ConfirmedTransactions, *tx.Nonce) + } + } + + if len(m.ConfirmedTransactions) > maxQueuedTransactions { + prunedTxIDs := m.pruneConfirmedTransactions() + m.lggr.Debugf("Confirmed transactions map for address: %v reached max limit of: %d. Pruned 1/%d of the oldest confirmed transactions. TxIDs: %v", + m.address, maxQueuedTransactions, pruneSubset, prunedTxIDs) + } + sort.Slice(confirmedTransactions, func(i, j int) bool { return confirmedTransactions[i].ID < confirmedTransactions[j].ID }) + sort.Slice(unconfirmedTransactionIDs, func(i, j int) bool { return unconfirmedTransactionIDs[i] < unconfirmedTransactionIDs[j] }) + return confirmedTransactions, unconfirmedTransactionIDs, nil +} + +func (m *InMemoryStore) MarkUnconfirmedTransactionPurgeable(nonce uint64) error { + m.Lock() + defer m.Unlock() + + tx, exists := m.UnconfirmedTransactions[nonce] + if !exists { + return fmt.Errorf("unconfirmed tx with nonce: %d was not found", nonce) + } + + tx.IsPurgeable = true + + return nil +} + +func (m *InMemoryStore) UpdateTransactionBroadcast(txID uint64, txNonce uint64, attemptHash common.Hash) error { + m.Lock() + defer m.Unlock() + + unconfirmedTx, exists := m.UnconfirmedTransactions[txNonce] + if !exists { + return fmt.Errorf("unconfirmed tx was not found for nonce: %d - txID: %v", txNonce, txID) + } + + // Set the same time for both the tx and its attempt + now := time.Now() + unconfirmedTx.LastBroadcastAt = &now + if unconfirmedTx.InitialBroadcastAt == nil { + unconfirmedTx.InitialBroadcastAt = &now + } + a, err := unconfirmedTx.FindAttemptByHash(attemptHash) + if err != nil { + return fmt.Errorf("UpdateTransactionBroadcast failed to find attempt. %w", err) + } + a.BroadcastAt = &now + + return nil +} + +func (m *InMemoryStore) UpdateUnstartedTransactionWithNonce(nonce uint64) (*types.Transaction, error) { + m.Lock() + defer m.Unlock() + + if len(m.UnstartedTransactions) == 0 { + m.lggr.Debugf("Unstarted transactions queue is empty for address: %v", m.address) + return nil, nil + } + + if tx, exists := m.UnconfirmedTransactions[nonce]; exists { + return nil, fmt.Errorf("an unconfirmed tx with the same nonce already exists: %v", tx) + } + + tx := m.UnstartedTransactions[0] + tx.Nonce = &nonce + tx.State = txmgr.TxUnconfirmed + + m.UnstartedTransactions = m.UnstartedTransactions[1:] + m.UnconfirmedTransactions[nonce] = tx + + return tx.DeepCopy(), nil +} + +// Shouldn't call lock because it's being called by a method that already has the lock +func (m *InMemoryStore) pruneConfirmedTransactions() []uint64 { + noncesToPrune := make([]uint64, 0, len(m.ConfirmedTransactions)) + for nonce := range m.ConfirmedTransactions { + noncesToPrune = append(noncesToPrune, nonce) + } + if len(noncesToPrune) == 0 { + return nil + } + sort.Slice(noncesToPrune, func(i, j int) bool { return noncesToPrune[i] < noncesToPrune[j] }) + minNonce := noncesToPrune[len(noncesToPrune)/pruneSubset] + + var txIDsToPrune []uint64 + for nonce, tx := range m.ConfirmedTransactions { + if nonce < minNonce { + txIDsToPrune = append(txIDsToPrune, tx.ID) + delete(m.Transactions, tx.ID) + delete(m.ConfirmedTransactions, nonce) + } + } + + sort.Slice(txIDsToPrune, func(i, j int) bool { return txIDsToPrune[i] < txIDsToPrune[j] }) + return txIDsToPrune +} + +// Error Handler +func (m *InMemoryStore) DeleteAttemptForUnconfirmedTx(transactionNonce uint64, attempt *types.Attempt) error { + m.Lock() + defer m.Unlock() + + tx, exists := m.UnconfirmedTransactions[transactionNonce] + if !exists { + return fmt.Errorf("unconfirmed tx was not found for nonce: %d - txID: %v", transactionNonce, attempt.TxID) + } + + for i, a := range tx.Attempts { + if a.Hash == attempt.Hash { + tx.Attempts = append(tx.Attempts[:i], tx.Attempts[i+1:]...) + return nil + } + } + + return fmt.Errorf("attempt with hash: %v for txID: %v was not found", attempt.Hash, attempt.TxID) +} + +func (m *InMemoryStore) MarkTxFatal(*types.Transaction) error { + return errors.New("not implemented") +} + +// Orchestrator +func (m *InMemoryStore) FindTxWithIdempotencyKey(idempotencyKey string) *types.Transaction { + m.RLock() + defer m.RUnlock() + + for _, tx := range m.Transactions { + if tx.IdempotencyKey != nil && *tx.IdempotencyKey == idempotencyKey { + return tx.DeepCopy() + } + } + + return nil +} diff --git a/core/chains/evm/txm/storage/inmemory_store_manager.go b/core/chains/evm/txm/storage/inmemory_store_manager.go new file mode 100644 index 00000000000..a7538823eea --- /dev/null +++ b/core/chains/evm/txm/storage/inmemory_store_manager.go @@ -0,0 +1,136 @@ +package storage + +import ( + "context" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +const StoreNotFoundForAddress string = "InMemoryStore for address: %v not found" + +type InMemoryStoreManager struct { + lggr logger.Logger + chainID *big.Int + InMemoryStoreMap map[common.Address]*InMemoryStore +} + +func NewInMemoryStoreManager(lggr logger.Logger, chainID *big.Int) *InMemoryStoreManager { + inMemoryStoreMap := make(map[common.Address]*InMemoryStore) + return &InMemoryStoreManager{ + lggr: lggr, + chainID: chainID, + InMemoryStoreMap: inMemoryStoreMap} +} + +func (m *InMemoryStoreManager) AbandonPendingTransactions(_ context.Context, fromAddress common.Address) error { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + store.AbandonPendingTransactions() + return nil + } + return fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) Add(addresses ...common.Address) (err error) { + for _, address := range addresses { + if _, exists := m.InMemoryStoreMap[address]; exists { + err = errors.Join(err, fmt.Errorf("address %v already exists in store manager", address)) + } + m.InMemoryStoreMap[address] = NewInMemoryStore(m.lggr, address, m.chainID) + } + return +} + +func (m *InMemoryStoreManager) AppendAttemptToTransaction(_ context.Context, txNonce uint64, fromAddress common.Address, attempt *types.Attempt) error { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + return store.AppendAttemptToTransaction(txNonce, attempt) + } + return fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) CountUnstartedTransactions(fromAddress common.Address) (int, error) { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + return store.CountUnstartedTransactions(), nil + } + return 0, fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) CreateEmptyUnconfirmedTransaction(_ context.Context, fromAddress common.Address, nonce uint64, gasLimit uint64) (*types.Transaction, error) { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + return store.CreateEmptyUnconfirmedTransaction(nonce, gasLimit) + } + return nil, fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) CreateTransaction(_ context.Context, txRequest *types.TxRequest) (*types.Transaction, error) { + if store, exists := m.InMemoryStoreMap[txRequest.FromAddress]; exists { + return store.CreateTransaction(txRequest), nil + } + return nil, fmt.Errorf(StoreNotFoundForAddress, txRequest.FromAddress) +} + +func (m *InMemoryStoreManager) FetchUnconfirmedTransactionAtNonceWithCount(_ context.Context, nonce uint64, fromAddress common.Address) (tx *types.Transaction, count int, err error) { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + tx, count = store.FetchUnconfirmedTransactionAtNonceWithCount(nonce) + return + } + return nil, 0, fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) MarkConfirmedAndReorgedTransactions(_ context.Context, nonce uint64, fromAddress common.Address) (confirmedTxs []*types.Transaction, unconfirmedTxIDs []uint64, err error) { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + confirmedTxs, unconfirmedTxIDs, err = store.MarkConfirmedAndReorgedTransactions(nonce) + return + } + return nil, nil, fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) MarkUnconfirmedTransactionPurgeable(_ context.Context, nonce uint64, fromAddress common.Address) error { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + return store.MarkUnconfirmedTransactionPurgeable(nonce) + } + return fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) UpdateTransactionBroadcast(_ context.Context, txID uint64, nonce uint64, attemptHash common.Hash, fromAddress common.Address) error { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + return store.UpdateTransactionBroadcast(txID, nonce, attemptHash) + } + return fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) UpdateUnstartedTransactionWithNonce(_ context.Context, fromAddress common.Address, nonce uint64) (*types.Transaction, error) { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + return store.UpdateUnstartedTransactionWithNonce(nonce) + } + return nil, fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) DeleteAttemptForUnconfirmedTx(_ context.Context, nonce uint64, attempt *types.Attempt, fromAddress common.Address) error { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + return store.DeleteAttemptForUnconfirmedTx(nonce, attempt) + } + return fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) MarkTxFatal(_ context.Context, tx *types.Transaction, fromAddress common.Address) error { + if store, exists := m.InMemoryStoreMap[fromAddress]; exists { + return store.MarkTxFatal(tx) + } + return fmt.Errorf(StoreNotFoundForAddress, fromAddress) +} + +func (m *InMemoryStoreManager) FindTxWithIdempotencyKey(_ context.Context, idempotencyKey string) (*types.Transaction, error) { + for _, store := range m.InMemoryStoreMap { + tx := store.FindTxWithIdempotencyKey(idempotencyKey) + if tx != nil { + return tx, nil + } + } + return nil, nil +} diff --git a/core/chains/evm/txm/storage/inmemory_store_manager_test.go b/core/chains/evm/txm/storage/inmemory_store_manager_test.go new file mode 100644 index 00000000000..aff589fb9e1 --- /dev/null +++ b/core/chains/evm/txm/storage/inmemory_store_manager_test.go @@ -0,0 +1,36 @@ +package storage + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils" +) + +func TestAdd(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + m := NewInMemoryStoreManager(logger.Test(t), testutils.FixtureChainID) + // Adds a new address + err := m.Add(fromAddress) + require.NoError(t, err) + assert.Len(t, m.InMemoryStoreMap, 1) + + // Fails if address exists + err = m.Add(fromAddress) + require.Error(t, err) + + // Adds multiple addresses + fromAddress1 := testutils.NewAddress() + fromAddress2 := testutils.NewAddress() + addresses := []common.Address{fromAddress1, fromAddress2} + err = m.Add(addresses...) + require.NoError(t, err) + assert.Len(t, m.InMemoryStoreMap, 3) +} diff --git a/core/chains/evm/txm/storage/inmemory_store_test.go b/core/chains/evm/txm/storage/inmemory_store_test.go new file mode 100644 index 00000000000..226cf284bba --- /dev/null +++ b/core/chains/evm/txm/storage/inmemory_store_test.go @@ -0,0 +1,559 @@ +package storage + +import ( + "fmt" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink/v2/common/txmgr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +func TestAbandonPendingTransactions(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + t.Run("abandons unstarted and unconfirmed transactions", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + // Unstarted + tx1 := insertUnstartedTransaction(m) + tx2 := insertUnstartedTransaction(m) + + // Unconfirmed + tx3, err := insertUnconfirmedTransaction(m, 3) + require.NoError(t, err) + tx4, err := insertUnconfirmedTransaction(m, 4) + require.NoError(t, err) + + m.AbandonPendingTransactions() + + assert.Equal(t, txmgr.TxFatalError, tx1.State) + assert.Equal(t, txmgr.TxFatalError, tx2.State) + assert.Equal(t, txmgr.TxFatalError, tx3.State) + assert.Equal(t, txmgr.TxFatalError, tx4.State) + }) + + t.Run("skips all types apart from unstarted and unconfirmed transactions", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + // Fatal + tx1 := insertFataTransaction(m) + tx2 := insertFataTransaction(m) + + // Confirmed + tx3, err := insertConfirmedTransaction(m, 3) + require.NoError(t, err) + tx4, err := insertConfirmedTransaction(m, 4) + require.NoError(t, err) + + m.AbandonPendingTransactions() + + assert.Equal(t, txmgr.TxFatalError, tx1.State) + assert.Equal(t, txmgr.TxFatalError, tx2.State) + assert.Equal(t, txmgr.TxConfirmed, tx3.State) + assert.Equal(t, txmgr.TxConfirmed, tx4.State) + assert.Len(t, m.Transactions, 2) // tx1, tx2 were dropped + }) +} + +func TestAppendAttemptToTransaction(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + + _, err := insertUnconfirmedTransaction(m, 10) // txID = 1, nonce = 10 + require.NoError(t, err) + _, err = insertConfirmedTransaction(m, 2) // txID = 2, nonce = 2 + require.NoError(t, err) + + t.Run("fails if corresponding unconfirmed transaction for attempt was not found", func(t *testing.T) { + var nonce uint64 = 1 + newAttempt := &types.Attempt{} + err := m.AppendAttemptToTransaction(nonce, newAttempt) + require.Error(t, err) + require.ErrorContains(t, err, "unconfirmed tx was not found") + }) + + t.Run("fails if unconfirmed transaction was found but doesn't match the txID", func(t *testing.T) { + var nonce uint64 = 10 + newAttempt := &types.Attempt{ + TxID: 2, + } + err := m.AppendAttemptToTransaction(nonce, newAttempt) + require.Error(t, err) + require.ErrorContains(t, err, "attempt points to a different txID") + }) + + t.Run("appends attempt to transaction", func(t *testing.T) { + var nonce uint64 = 10 + newAttempt := &types.Attempt{ + TxID: 1, + } + require.NoError(t, m.AppendAttemptToTransaction(nonce, newAttempt)) + tx, _ := m.FetchUnconfirmedTransactionAtNonceWithCount(10) + assert.Len(t, tx.Attempts, 1) + assert.Equal(t, uint16(1), tx.AttemptCount) + assert.False(t, tx.Attempts[0].CreatedAt.IsZero()) + }) +} + +func TestCountUnstartedTransactions(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + + assert.Equal(t, 0, m.CountUnstartedTransactions()) + + insertUnstartedTransaction(m) + assert.Equal(t, 1, m.CountUnstartedTransactions()) + + _, err := insertConfirmedTransaction(m, 10) + require.NoError(t, err) + assert.Equal(t, 1, m.CountUnstartedTransactions()) +} + +func TestCreateEmptyUnconfirmedTransaction(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + _, err := insertUnconfirmedTransaction(m, 1) + require.NoError(t, err) + _, err = insertConfirmedTransaction(m, 0) + require.NoError(t, err) + + t.Run("fails if unconfirmed transaction with the same nonce exists", func(t *testing.T) { + _, err := m.CreateEmptyUnconfirmedTransaction(1, 0) + require.Error(t, err) + }) + + t.Run("fails if confirmed transaction with the same nonce exists", func(t *testing.T) { + _, err := m.CreateEmptyUnconfirmedTransaction(0, 0) + require.Error(t, err) + }) + + t.Run("creates a new empty unconfirmed transaction", func(t *testing.T) { + tx, err := m.CreateEmptyUnconfirmedTransaction(2, 0) + require.NoError(t, err) + assert.Equal(t, txmgr.TxUnconfirmed, tx.State) + }) +} + +func TestCreateTransaction(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + + t.Run("creates new transactions", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + now := time.Now() + txR1 := &types.TxRequest{} + txR2 := &types.TxRequest{} + tx1 := m.CreateTransaction(txR1) + assert.Equal(t, uint64(0), tx1.ID) + assert.LessOrEqual(t, now, tx1.CreatedAt) + + tx2 := m.CreateTransaction(txR2) + assert.Equal(t, uint64(1), tx2.ID) + assert.LessOrEqual(t, now, tx2.CreatedAt) + + assert.Equal(t, 2, m.CountUnstartedTransactions()) + }) + + t.Run("prunes oldest unstarted transactions if limit is reached", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + overshot := 5 + for i := 0; i < maxQueuedTransactions+overshot; i++ { + r := &types.TxRequest{} + tx := m.CreateTransaction(r) + //nolint:gosec // this won't overflow + assert.Equal(t, uint64(i), tx.ID) + } + // total shouldn't exceed maxQueuedTransactions + assert.Equal(t, maxQueuedTransactions, m.CountUnstartedTransactions()) + // earliest tx ID should be the same amount of the number of transactions that we dropped + tx, err := m.UpdateUnstartedTransactionWithNonce(0) + require.NoError(t, err) + //nolint:gosec // this won't overflow + assert.Equal(t, uint64(overshot), tx.ID) + }) +} + +func TestFetchUnconfirmedTransactionAtNonceWithCount(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + + tx, count := m.FetchUnconfirmedTransactionAtNonceWithCount(0) + assert.Nil(t, tx) + assert.Equal(t, 0, count) + + var nonce uint64 + _, err := insertUnconfirmedTransaction(m, nonce) + require.NoError(t, err) + tx, count = m.FetchUnconfirmedTransactionAtNonceWithCount(0) + assert.Equal(t, *tx.Nonce, nonce) + assert.Equal(t, 1, count) +} + +func TestMarkConfirmedAndReorgedTransactions(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + + t.Run("returns 0 if there are no transactions", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + un, cn, err := m.MarkConfirmedAndReorgedTransactions(100) + require.NoError(t, err) + assert.Empty(t, un) + assert.Empty(t, cn) + }) + + t.Run("confirms transaction with nonce lower than the latest", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + ctx1, err := insertUnconfirmedTransaction(m, 0) + require.NoError(t, err) + + ctx2, err := insertUnconfirmedTransaction(m, 1) + require.NoError(t, err) + + ctxs, utxs, err := m.MarkConfirmedAndReorgedTransactions(1) + require.NoError(t, err) + assert.Equal(t, txmgr.TxConfirmed, ctx1.State) + assert.Equal(t, txmgr.TxUnconfirmed, ctx2.State) + assert.Equal(t, ctxs[0].ID, ctx1.ID) // Ensure order + assert.Empty(t, utxs) + }) + + t.Run("state remains the same if nonce didn't change", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + ctx1, err := insertConfirmedTransaction(m, 0) + require.NoError(t, err) + + ctx2, err := insertUnconfirmedTransaction(m, 1) + require.NoError(t, err) + + ctxs, utxs, err := m.MarkConfirmedAndReorgedTransactions(1) + require.NoError(t, err) + assert.Equal(t, txmgr.TxConfirmed, ctx1.State) + assert.Equal(t, txmgr.TxUnconfirmed, ctx2.State) + assert.Empty(t, ctxs) + assert.Empty(t, utxs) + }) + + t.Run("unconfirms transaction with nonce equal to or higher than the latest", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + ctx1, err := insertConfirmedTransaction(m, 0) + require.NoError(t, err) + + ctx2, err := insertConfirmedTransaction(m, 1) + require.NoError(t, err) + + ctxs, utxs, err := m.MarkConfirmedAndReorgedTransactions(1) + require.NoError(t, err) + assert.Equal(t, txmgr.TxConfirmed, ctx1.State) + assert.Equal(t, txmgr.TxUnconfirmed, ctx2.State) + assert.Equal(t, utxs[0], ctx2.ID) + assert.Empty(t, ctxs) + }) + + t.Run("logs an error during confirmation if a transaction with the same nonce already exists", func(t *testing.T) { + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + m := NewInMemoryStore(lggr, fromAddress, testutils.FixtureChainID) + _, err := insertConfirmedTransaction(m, 0) + require.NoError(t, err) + _, err = insertUnconfirmedTransaction(m, 0) + require.NoError(t, err) + + _, _, err = m.MarkConfirmedAndReorgedTransactions(1) + require.NoError(t, err) + tests.AssertLogEventually(t, observedLogs, "Another confirmed transaction with the same nonce exists") + }) + + t.Run("prunes confirmed transactions map if it reaches the limit", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + overshot := 5 + for i := 0; i < maxQueuedTransactions+overshot; i++ { + //nolint:gosec // this won't overflow + _, err := insertConfirmedTransaction(m, uint64(i)) + require.NoError(t, err) + } + assert.Len(t, m.ConfirmedTransactions, maxQueuedTransactions+overshot) + //nolint:gosec // this won't overflow + _, _, err := m.MarkConfirmedAndReorgedTransactions(uint64(maxQueuedTransactions + overshot)) + require.NoError(t, err) + assert.Len(t, m.ConfirmedTransactions, 170) + }) +} + +func TestMarkUnconfirmedTransactionPurgeable(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + + // fails if tx was not found + err := m.MarkUnconfirmedTransactionPurgeable(0) + require.Error(t, err) + + tx, err := insertUnconfirmedTransaction(m, 0) + require.NoError(t, err) + err = m.MarkUnconfirmedTransactionPurgeable(0) + require.NoError(t, err) + assert.True(t, tx.IsPurgeable) +} + +func TestUpdateTransactionBroadcast(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + hash := testutils.NewHash() + t.Run("fails if unconfirmed transaction was not found", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + var nonce uint64 + require.Error(t, m.UpdateTransactionBroadcast(0, nonce, hash)) + }) + + t.Run("fails if attempt was not found for a given transaction", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + var nonce uint64 + tx, err := insertUnconfirmedTransaction(m, nonce) + require.NoError(t, err) + require.Error(t, m.UpdateTransactionBroadcast(0, nonce, hash)) + + // Attempt with different hash + attempt := &types.Attempt{TxID: tx.ID, Hash: testutils.NewHash()} + tx.Attempts = append(tx.Attempts, attempt) + require.Error(t, m.UpdateTransactionBroadcast(0, nonce, hash)) + }) + + t.Run("updates transaction's and attempt's broadcast times", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + var nonce uint64 + tx, err := insertUnconfirmedTransaction(m, nonce) + require.NoError(t, err) + attempt := &types.Attempt{TxID: tx.ID, Hash: hash} + tx.Attempts = append(tx.Attempts, attempt) + require.NoError(t, m.UpdateTransactionBroadcast(0, nonce, hash)) + assert.False(t, tx.LastBroadcastAt.IsZero()) + assert.False(t, attempt.BroadcastAt.IsZero()) + assert.False(t, tx.InitialBroadcastAt.IsZero()) + }) +} + +func TestUpdateUnstartedTransactionWithNonce(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + t.Run("returns nil if there are no unstarted transactions", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + tx, err := m.UpdateUnstartedTransactionWithNonce(0) + require.NoError(t, err) + assert.Nil(t, tx) + }) + + t.Run("fails if there is already another unconfirmed transaction with the same nonce", func(t *testing.T) { + var nonce uint64 + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + insertUnstartedTransaction(m) + _, err := insertUnconfirmedTransaction(m, nonce) + require.NoError(t, err) + + _, err = m.UpdateUnstartedTransactionWithNonce(nonce) + require.Error(t, err) + }) + + t.Run("updates unstarted transaction to unconfirmed and assigns a nonce", func(t *testing.T) { + var nonce uint64 + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + insertUnstartedTransaction(m) + + tx, err := m.UpdateUnstartedTransactionWithNonce(nonce) + require.NoError(t, err) + assert.Equal(t, nonce, *tx.Nonce) + assert.Equal(t, txmgr.TxUnconfirmed, tx.State) + assert.Empty(t, m.UnstartedTransactions) + }) +} + +func TestDeleteAttemptForUnconfirmedTx(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + t.Run("fails if corresponding unconfirmed transaction for attempt was not found", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + var nonce uint64 + tx := &types.Transaction{Nonce: &nonce} + attempt := &types.Attempt{TxID: 0} + err := m.DeleteAttemptForUnconfirmedTx(*tx.Nonce, attempt) + require.Error(t, err) + }) + + t.Run("fails if corresponding unconfirmed attempt for txID was not found", func(t *testing.T) { + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + _, err := insertUnconfirmedTransaction(m, 0) + require.NoError(t, err) + + attempt := &types.Attempt{TxID: 2, Hash: testutils.NewHash()} + err = m.DeleteAttemptForUnconfirmedTx(0, attempt) + + require.Error(t, err) + }) + + t.Run("deletes attempt of unconfirmed transaction", func(t *testing.T) { + hash := testutils.NewHash() + var nonce uint64 + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + tx, err := insertUnconfirmedTransaction(m, nonce) + require.NoError(t, err) + + attempt := &types.Attempt{TxID: 0, Hash: hash} + tx.Attempts = append(tx.Attempts, attempt) + err = m.DeleteAttemptForUnconfirmedTx(nonce, attempt) + require.NoError(t, err) + + assert.Empty(t, tx.Attempts) + }) +} + +func TestFindTxWithIdempotencyKey(t *testing.T) { + t.Parallel() + fromAddress := testutils.NewAddress() + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + tx, err := insertConfirmedTransaction(m, 0) + require.NoError(t, err) + + ik := "IK" + tx.IdempotencyKey = &ik + itx := m.FindTxWithIdempotencyKey(ik) + assert.Equal(t, ik, *itx.IdempotencyKey) + + uik := "Unknown" + itx = m.FindTxWithIdempotencyKey(uik) + assert.Nil(t, itx) +} + +func TestPruneConfirmedTransactions(t *testing.T) { + t.Parallel() + fromAddress := testutils.NewAddress() + m := NewInMemoryStore(logger.Test(t), fromAddress, testutils.FixtureChainID) + total := 5 + for i := 0; i < total; i++ { + //nolint:gosec // this won't overflow + _, err := insertConfirmedTransaction(m, uint64(i)) + require.NoError(t, err) + } + prunedTxIDs := m.pruneConfirmedTransactions() + left := total - total/pruneSubset + assert.Len(t, m.ConfirmedTransactions, left) + assert.Len(t, prunedTxIDs, total/pruneSubset) +} + +func insertUnstartedTransaction(m *InMemoryStore) *types.Transaction { + m.Lock() + defer m.Unlock() + + var nonce uint64 + m.txIDCount++ + tx := &types.Transaction{ + ID: m.txIDCount, + ChainID: testutils.FixtureChainID, + Nonce: &nonce, + FromAddress: m.address, + ToAddress: testutils.NewAddress(), + Value: big.NewInt(0), + SpecifiedGasLimit: 0, + CreatedAt: time.Now(), + State: txmgr.TxUnstarted, + } + + m.UnstartedTransactions = append(m.UnstartedTransactions, tx) + m.Transactions[tx.ID] = tx + return tx +} + +func insertUnconfirmedTransaction(m *InMemoryStore, nonce uint64) (*types.Transaction, error) { + m.Lock() + defer m.Unlock() + + m.txIDCount++ + tx := &types.Transaction{ + ID: m.txIDCount, + ChainID: testutils.FixtureChainID, + Nonce: &nonce, + FromAddress: m.address, + ToAddress: testutils.NewAddress(), + Value: big.NewInt(0), + SpecifiedGasLimit: 0, + CreatedAt: time.Now(), + State: txmgr.TxUnconfirmed, + } + + if _, exists := m.UnconfirmedTransactions[nonce]; exists { + return nil, fmt.Errorf("an unconfirmed tx with the same nonce already exists: %v", m.UnconfirmedTransactions[nonce]) + } + + m.UnconfirmedTransactions[nonce] = tx + m.Transactions[tx.ID] = tx + return tx, nil +} + +func insertConfirmedTransaction(m *InMemoryStore, nonce uint64) (*types.Transaction, error) { + m.Lock() + defer m.Unlock() + + m.txIDCount++ + tx := &types.Transaction{ + ID: m.txIDCount, + ChainID: testutils.FixtureChainID, + Nonce: &nonce, + FromAddress: m.address, + ToAddress: testutils.NewAddress(), + Value: big.NewInt(0), + SpecifiedGasLimit: 0, + CreatedAt: time.Now(), + State: txmgr.TxConfirmed, + } + + if _, exists := m.ConfirmedTransactions[nonce]; exists { + return nil, fmt.Errorf("a confirmed tx with the same nonce already exists: %v", m.ConfirmedTransactions[nonce]) + } + + m.ConfirmedTransactions[nonce] = tx + m.Transactions[tx.ID] = tx + return tx, nil +} + +func insertFataTransaction(m *InMemoryStore) *types.Transaction { + m.Lock() + defer m.Unlock() + + var nonce uint64 + m.txIDCount++ + tx := &types.Transaction{ + ID: m.txIDCount, + ChainID: testutils.FixtureChainID, + Nonce: &nonce, + FromAddress: m.address, + ToAddress: testutils.NewAddress(), + Value: big.NewInt(0), + SpecifiedGasLimit: 0, + CreatedAt: time.Now(), + State: txmgr.TxFatalError, + } + + m.FatalTransactions = append(m.FatalTransactions, tx) + m.Transactions[tx.ID] = tx + return tx +} diff --git a/core/chains/evm/txm/stuck_tx_detector.go b/core/chains/evm/txm/stuck_tx_detector.go new file mode 100644 index 00000000000..2da4ad4cd67 --- /dev/null +++ b/core/chains/evm/txm/stuck_tx_detector.go @@ -0,0 +1,123 @@ +package txm + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +type StuckTxDetectorConfig struct { + BlockTime time.Duration + StuckTxBlockThreshold uint32 + DetectionURL string + DualBroadcast bool +} + +type stuckTxDetector struct { + lggr logger.Logger + chainType chaintype.ChainType + config StuckTxDetectorConfig + lastPurgeMap map[common.Address]time.Time +} + +func NewStuckTxDetector(lggr logger.Logger, chaintype chaintype.ChainType, config StuckTxDetectorConfig) *stuckTxDetector { + return &stuckTxDetector{ + lggr: lggr, + chainType: chaintype, + config: config, + lastPurgeMap: make(map[common.Address]time.Time), + } +} + +func (s *stuckTxDetector) DetectStuckTransaction(ctx context.Context, tx *types.Transaction) (bool, error) { + //nolint:gocritic //placeholder for upcoming chaintypes + switch s.chainType { + default: + return s.timeBasedDetection(tx), nil + } +} + +// timeBasedDetection marks a transaction if all the following conditions are met: +// - LastBroadcastAt is not nil +// - Time since last broadcast is above the threshold +// - Time since last purge is above threshold +// +// NOTE: Potentially we can use a subset of threhsold for last purge check, because the transaction would have already been broadcasted to the mempool +// so it is more likely to be picked up compared to a transaction that hasn't been broadcasted before. This would avoid slowing down TXM for sebsequent transactions +// in case the current one is stuck. +func (s *stuckTxDetector) timeBasedDetection(tx *types.Transaction) bool { + threshold := (s.config.BlockTime * time.Duration(s.config.StuckTxBlockThreshold)) + if tx.LastBroadcastAt != nil && min(time.Since(*tx.LastBroadcastAt), time.Since(s.lastPurgeMap[tx.FromAddress])) > threshold { + s.lggr.Debugf("TxID: %v last broadcast was: %v and last purge: %v which is more than the max configured duration: %v. Transaction is now considered stuck and will be purged.", + tx.ID, tx.LastBroadcastAt, s.lastPurgeMap[tx.FromAddress], threshold) + s.lastPurgeMap[tx.FromAddress] = time.Now() + return true + } + return false +} + +type APIResponse struct { + Status string `json:"status,omitempty"` + Hash common.Hash `json:"hash,omitempty"` +} + +const ( + APIStatusPending = "PENDING" + APIStatusIncluded = "INCLUDED" + APIStatusFailed = "FAILED" + APIStatusCancelled = "CANCELLED" + APIStatusUnknown = "UNKNOWN" +) + +// Deprecated: DualBroadcastDetection doesn't provide any significant benefits in terms of speed and time +// based detection can replace it. +func (s *stuckTxDetector) DualBroadcastDetection(ctx context.Context, tx *types.Transaction) (bool, error) { + for _, attempt := range tx.Attempts { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.config.DetectionURL+attempt.Hash.String(), nil) + if err != nil { + return false, fmt.Errorf("failed to make request for txID: %v, attemptHash: %v - %w", tx.ID, attempt.Hash, err) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return false, fmt.Errorf("failed to get transaction status for txID: %v, attemptHash: %v - %w", tx.ID, attempt.Hash, err) + } + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return false, fmt.Errorf("request %v failed with status: %d", req, resp.StatusCode) + } + body, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return false, err + } + + var apiResponse APIResponse + err = json.Unmarshal(body, &apiResponse) + if err != nil { + return false, fmt.Errorf("failed to unmarshal response for txID: %v, attemptHash: %v - %w: %s", tx.ID, attempt.Hash, err, string(body)) + } + switch apiResponse.Status { + case APIStatusPending, APIStatusIncluded: + return false, nil + case APIStatusFailed, APIStatusCancelled: + s.lggr.Debugf("TxID: %v with attempHash: %v was marked as failed/cancelled by the RPC. Transaction is now considered stuck and will be purged.", + tx.ID, attempt.Hash) + return true, nil + case APIStatusUnknown: + continue + default: + continue + } + } + return false, nil +} diff --git a/core/chains/evm/txm/stuck_tx_detector_test.go b/core/chains/evm/txm/stuck_tx_detector_test.go new file mode 100644 index 00000000000..af5a765dcdb --- /dev/null +++ b/core/chains/evm/txm/stuck_tx_detector_test.go @@ -0,0 +1,80 @@ +package txm + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/testutils" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +func TestTimeBasedDetection(t *testing.T) { + t.Parallel() + + t.Run("returns false if transaction is not stuck", func(t *testing.T) { + config := StuckTxDetectorConfig{ + BlockTime: 10 * time.Second, + StuckTxBlockThreshold: 5, + } + fromAddress := testutils.NewAddress() + s := NewStuckTxDetector(logger.Test(t), "", config) + + // No previous broadcast + tx := &types.Transaction{ + ID: 1, + LastBroadcastAt: nil, + FromAddress: fromAddress, + } + assert.False(t, s.timeBasedDetection(tx)) + // Not enough time has passed since last broadcast + now := time.Now() + tx.LastBroadcastAt = &now + assert.False(t, s.timeBasedDetection(tx)) + // Not enough time has passed since last purge + tx.LastBroadcastAt = &time.Time{} + s.lastPurgeMap[fromAddress] = now + assert.False(t, s.timeBasedDetection(tx)) + }) + + t.Run("returns true if transaction is stuck", func(t *testing.T) { + config := StuckTxDetectorConfig{ + BlockTime: 10 * time.Second, + StuckTxBlockThreshold: 5, + } + fromAddress := testutils.NewAddress() + s := NewStuckTxDetector(logger.Test(t), "", config) + + tx := &types.Transaction{ + ID: 1, + LastBroadcastAt: &time.Time{}, + FromAddress: fromAddress, + } + assert.True(t, s.timeBasedDetection(tx)) + }) + + t.Run("marks first tx as stuck, updates purge time for address, and returns false for the second tx with the same broadcast time", func(t *testing.T) { + config := StuckTxDetectorConfig{ + BlockTime: 1 * time.Second, + StuckTxBlockThreshold: 10, + } + fromAddress := testutils.NewAddress() + s := NewStuckTxDetector(logger.Test(t), "", config) + + tx1 := &types.Transaction{ + ID: 1, + LastBroadcastAt: &time.Time{}, + FromAddress: fromAddress, + } + tx2 := &types.Transaction{ + ID: 2, + LastBroadcastAt: &time.Time{}, + FromAddress: fromAddress, + } + assert.True(t, s.timeBasedDetection(tx1)) + assert.False(t, s.timeBasedDetection(tx2)) + }) +} diff --git a/core/chains/evm/txm/txm.go b/core/chains/evm/txm/txm.go new file mode 100644 index 00000000000..c8b4b6f1688 --- /dev/null +++ b/core/chains/evm/txm/txm.go @@ -0,0 +1,447 @@ +package txm + +import ( + "context" + "fmt" + "math/big" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/jpillora/backoff" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" + "github.com/smartcontractkit/chainlink-common/pkg/utils" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +const ( + broadcastInterval time.Duration = 30 * time.Second + maxInFlightTransactions int = 16 + maxInFlightSubset int = 5 + maxAllowedAttempts uint16 = 10 + pendingNonceDefaultTimeout time.Duration = 30 * time.Second + pendingNonceRecheckInterval time.Duration = 1 * time.Second +) + +type Client interface { + PendingNonceAt(context.Context, common.Address) (uint64, error) + NonceAt(context.Context, common.Address, *big.Int) (uint64, error) + SendTransaction(ctx context.Context, tx *types.Transaction, attempt *types.Attempt) error +} + +type TxStore interface { + AbandonPendingTransactions(context.Context, common.Address) error + AppendAttemptToTransaction(context.Context, uint64, common.Address, *types.Attempt) error + CreateEmptyUnconfirmedTransaction(context.Context, common.Address, uint64, uint64) (*types.Transaction, error) + CreateTransaction(context.Context, *types.TxRequest) (*types.Transaction, error) + FetchUnconfirmedTransactionAtNonceWithCount(context.Context, uint64, common.Address) (*types.Transaction, int, error) + MarkConfirmedAndReorgedTransactions(context.Context, uint64, common.Address) ([]*types.Transaction, []uint64, error) + MarkUnconfirmedTransactionPurgeable(context.Context, uint64, common.Address) error + UpdateTransactionBroadcast(context.Context, uint64, uint64, common.Hash, common.Address) error + UpdateUnstartedTransactionWithNonce(context.Context, common.Address, uint64) (*types.Transaction, error) + + // ErrorHandler + DeleteAttemptForUnconfirmedTx(context.Context, uint64, *types.Attempt, common.Address) error + MarkTxFatal(context.Context, *types.Transaction, common.Address) error +} + +type AttemptBuilder interface { + NewAttempt(context.Context, logger.Logger, *types.Transaction, bool) (*types.Attempt, error) + NewBumpAttempt(context.Context, logger.Logger, *types.Transaction, types.Attempt) (*types.Attempt, error) +} + +type ErrorHandler interface { + HandleError(*types.Transaction, error, AttemptBuilder, Client, TxStore, func(common.Address, uint64), bool) (err error) +} + +type StuckTxDetector interface { + DetectStuckTransaction(ctx context.Context, tx *types.Transaction) (bool, error) +} + +type Keystore interface { + EnabledAddressesForChain(ctx context.Context, chainID *big.Int) (addresses []common.Address, err error) +} + +type Config struct { + EIP1559 bool + BlockTime time.Duration + RetryBlockThreshold uint16 + EmptyTxLimitDefault uint64 +} + +type Txm struct { + services.StateMachine + lggr logger.SugaredLogger + chainID *big.Int + client Client + attemptBuilder AttemptBuilder + errorHandler ErrorHandler + stuckTxDetector StuckTxDetector + txStore TxStore + keystore Keystore + config Config + metrics *txmMetrics + + nonceMapMu sync.RWMutex + nonceMap map[common.Address]uint64 + + triggerCh map[common.Address]chan struct{} + stopCh services.StopChan + wg sync.WaitGroup +} + +func NewTxm(lggr logger.Logger, chainID *big.Int, client Client, attemptBuilder AttemptBuilder, txStore TxStore, stuckTxDetector StuckTxDetector, config Config, keystore Keystore) *Txm { + return &Txm{ + lggr: logger.Sugared(logger.Named(lggr, "Txm")), + keystore: keystore, + chainID: chainID, + client: client, + attemptBuilder: attemptBuilder, + txStore: txStore, + stuckTxDetector: stuckTxDetector, + config: config, + nonceMap: make(map[common.Address]uint64), + triggerCh: make(map[common.Address]chan struct{}), + } +} + +func (t *Txm) Start(ctx context.Context) error { + return t.StartOnce("Txm", func() error { + tm, err := NewTxmMetrics(t.chainID) + if err != nil { + return err + } + t.metrics = tm + t.stopCh = make(chan struct{}) + + addresses, err := t.keystore.EnabledAddressesForChain(ctx, t.chainID) + if err != nil { + return err + } + for _, address := range addresses { + t.startAddress(address) + } + return nil + }) +} + +func (t *Txm) startAddress(address common.Address) { + triggerCh := make(chan struct{}, 1) + t.triggerCh[address] = triggerCh + + t.wg.Add(2) + go t.broadcastLoop(address, triggerCh) + go t.backfillLoop(address) +} + +func (t *Txm) initializeNonce(ctx context.Context, address common.Address) { + ctxWithTimeout, cancel := context.WithTimeout(ctx, pendingNonceDefaultTimeout) + defer cancel() + for { + pendingNonce, err := t.client.PendingNonceAt(ctxWithTimeout, address) + if err != nil { + t.lggr.Errorw("Error when fetching initial nonce", "address", address, "err", err) + select { + case <-time.After(pendingNonceRecheckInterval): + case <-ctx.Done(): + t.lggr.Errorw("context error", "err", context.Cause(ctx)) + return + } + continue + } + t.setNonce(address, pendingNonce) + t.lggr.Debugf("Set initial nonce for address: %v to %d", address, pendingNonce) + return + } +} + +func (t *Txm) Close() error { + return t.StopOnce("Txm", func() error { + close(t.stopCh) + t.wg.Wait() + return nil + }) +} + +func (t *Txm) HealthReport() map[string]error { + return map[string]error{t.lggr.Name(): t.Healthy()} +} + +func (t *Txm) CreateTransaction(ctx context.Context, txRequest *types.TxRequest) (tx *types.Transaction, err error) { + tx, err = t.txStore.CreateTransaction(ctx, txRequest) + if err == nil { + t.lggr.Infow("Created transaction", "tx", tx) + } + return +} + +func (t *Txm) Trigger(address common.Address) { + if !t.IfStarted(func() { + triggerCh, exists := t.triggerCh[address] + if !exists { + return + } + triggerCh <- struct{}{} + }) { + t.lggr.Error("Txm unstarted") + } +} + +func (t *Txm) Abandon(address common.Address) error { + // TODO: restart txm + t.lggr.Infof("Dropping unstarted and unconfirmed transactions for address: %v", address) + return t.txStore.AbandonPendingTransactions(context.TODO(), address) +} + +func (t *Txm) getNonce(address common.Address) uint64 { + t.nonceMapMu.RLock() + defer t.nonceMapMu.RUnlock() + return t.nonceMap[address] +} + +func (t *Txm) setNonce(address common.Address, nonce uint64) { + t.nonceMapMu.Lock() + t.nonceMap[address] = nonce + defer t.nonceMapMu.Unlock() +} + +func newBackoff(minDuration time.Duration) backoff.Backoff { + return backoff.Backoff{ + Min: minDuration, + Max: 1 * time.Minute, + Jitter: true, + } +} + +func (t *Txm) broadcastLoop(address common.Address, triggerCh chan struct{}) { + defer t.wg.Done() + ctx, cancel := t.stopCh.NewCtx() + defer cancel() + broadcastWithBackoff := newBackoff(1 * time.Second) + var broadcastCh <-chan time.Time + + t.initializeNonce(ctx, address) + + for { + start := time.Now() + bo, err := t.broadcastTransaction(ctx, address) + if err != nil { + t.lggr.Errorw("Error during transaction broadcasting", "err", err) + } else { + t.lggr.Debug("Transaction broadcasting time elapsed: ", time.Since(start)) + } + if bo { + broadcastCh = time.After(broadcastWithBackoff.Duration()) + } else { + broadcastWithBackoff.Reset() + broadcastCh = time.After(utils.WithJitter(broadcastInterval)) + } + select { + case <-ctx.Done(): + return + case <-triggerCh: + continue + case <-broadcastCh: + continue + } + } +} + +func (t *Txm) backfillLoop(address common.Address) { + defer t.wg.Done() + ctx, cancel := t.stopCh.NewCtx() + defer cancel() + backfillWithBackoff := newBackoff(t.config.BlockTime) + backfillCh := time.After(utils.WithJitter(t.config.BlockTime)) + + for { + select { + case <-ctx.Done(): + return + case <-backfillCh: + start := time.Now() + bo, err := t.backfillTransactions(ctx, address) + if err != nil { + t.lggr.Errorw("Error during backfill", "err", err) + } else { + t.lggr.Debug("Backfill time elapsed: ", time.Since(start)) + } + if bo { + backfillCh = time.After(backfillWithBackoff.Duration()) + } else { + backfillWithBackoff.Reset() + backfillCh = time.After(utils.WithJitter(t.config.BlockTime)) + } + } + } +} + +func (t *Txm) broadcastTransaction(ctx context.Context, address common.Address) (bool, error) { + for { + _, unconfirmedCount, err := t.txStore.FetchUnconfirmedTransactionAtNonceWithCount(ctx, 0, address) + if err != nil { + return false, err + } + + // Optimistically send up to maxInFlightSubset of the maxInFlightTransactions. After that threshold, broadcast more cautiously + // by checking the pending nonce so no more than maxInFlightSubset can get stuck simultaneously i.e. due + // to insufficient balance. We're making this trade-off to avoid storing stuck transactions and making unnecessary + // RPC calls. The upper limit is always maxInFlightTransactions regardless of the pending nonce. + if unconfirmedCount >= maxInFlightSubset { + if unconfirmedCount > maxInFlightTransactions { + t.lggr.Warnf("Reached transaction limit: %d for unconfirmed transactions", maxInFlightTransactions) + return true, nil + } + pendingNonce, e := t.client.PendingNonceAt(ctx, address) + if e != nil { + return false, e + } + nonce := t.getNonce(address) + if nonce > pendingNonce { + t.lggr.Warnf("Reached transaction limit. LocalNonce: %d, PendingNonce %d, unconfirmedCount: %d", + nonce, pendingNonce, unconfirmedCount) + return true, nil + } + } + + nonce := t.getNonce(address) + tx, err := t.txStore.UpdateUnstartedTransactionWithNonce(ctx, address, nonce) + if err != nil { + return false, err + } + if tx == nil { + return false, nil + } + t.setNonce(address, nonce+1) + + if err := t.createAndSendAttempt(ctx, tx, address); err != nil { + return false, err + } + } +} + +func (t *Txm) createAndSendAttempt(ctx context.Context, tx *types.Transaction, address common.Address) error { + attempt, err := t.attemptBuilder.NewAttempt(ctx, t.lggr, tx, t.config.EIP1559) + if err != nil { + return err + } + + if tx.Nonce == nil { + return fmt.Errorf("nonce for txID: %v is empty", tx.ID) + } + if err = t.txStore.AppendAttemptToTransaction(ctx, *tx.Nonce, address, attempt); err != nil { + return err + } + + return t.sendTransactionWithError(ctx, tx, attempt, address) +} + +func (t *Txm) sendTransactionWithError(ctx context.Context, tx *types.Transaction, attempt *types.Attempt, address common.Address) (err error) { + if tx.Nonce == nil { + return fmt.Errorf("nonce for txID: %v is empty", tx.ID) + } + start := time.Now() + txErr := t.client.SendTransaction(ctx, tx, attempt) + tx.AttemptCount++ + t.lggr.Infow("Broadcasted attempt", "tx", tx, "attempt", attempt, "duration", time.Since(start), "txErr: ", txErr) + if txErr != nil && t.errorHandler != nil { + if err = t.errorHandler.HandleError(tx, txErr, t.attemptBuilder, t.client, t.txStore, t.setNonce, false); err != nil { + return + } + } else if txErr != nil { + pendingNonce, err := t.client.PendingNonceAt(ctx, address) + if err != nil { + return err + } + if pendingNonce <= *tx.Nonce { + return fmt.Errorf("Pending nonce for txID: %v didn't increase. PendingNonce: %d, TxNonce: %d. TxErr: %w", tx.ID, pendingNonce, *tx.Nonce, txErr) + } + } + + t.metrics.IncrementNumBroadcastedTxs(ctx) + return t.txStore.UpdateTransactionBroadcast(ctx, attempt.TxID, *tx.Nonce, attempt.Hash, address) +} + +func (t *Txm) backfillTransactions(ctx context.Context, address common.Address) (bool, error) { + latestNonce, err := t.client.NonceAt(ctx, address, nil) + if err != nil { + return false, err + } + + confirmedTransactions, unconfirmedTransactionIDs, err := t.txStore.MarkConfirmedAndReorgedTransactions(ctx, latestNonce, address) + if err != nil { + return false, err + } + if len(confirmedTransactions) > 0 || len(unconfirmedTransactionIDs) > 0 { + t.metrics.IncrementNumConfirmedTxs(ctx, len(confirmedTransactions)) + confirmedTransactionIDs := t.extractMetrics(ctx, confirmedTransactions) + t.lggr.Infof("Confirmed transaction IDs: %v . Re-orged transaction IDs: %v", confirmedTransactionIDs, unconfirmedTransactionIDs) + } + + tx, unconfirmedCount, err := t.txStore.FetchUnconfirmedTransactionAtNonceWithCount(ctx, latestNonce, address) + if err != nil { + return false, err + } + if unconfirmedCount == 0 { + t.lggr.Debugf("All transactions confirmed for address: %v", address) + return false, err // TODO: add backoff to optimize requests + } + + if tx == nil || *tx.Nonce != latestNonce { + t.lggr.Warnf("Nonce gap at nonce: %d - address: %v. Creating a new transaction\n", latestNonce, address) + t.metrics.IncrementNumNonceGaps(ctx) + return false, t.createAndSendEmptyTx(ctx, latestNonce, address) + } else { //nolint:revive //easier to read + if !tx.IsPurgeable && t.stuckTxDetector != nil { + isStuck, err := t.stuckTxDetector.DetectStuckTransaction(ctx, tx) + if err != nil { + return false, err + } + if isStuck { + tx.IsPurgeable = true + err = t.txStore.MarkUnconfirmedTransactionPurgeable(ctx, *tx.Nonce, address) + if err != nil { + return false, err + } + t.lggr.Infof("Marked tx as purgeable. Sending purge attempt for txID: %d", tx.ID) + return false, t.createAndSendAttempt(ctx, tx, address) + } + } + + if tx.AttemptCount >= maxAllowedAttempts { + return true, fmt.Errorf("reached max allowed attempts for txID: %d. TXM won't broadcast any more attempts."+ + "If this error persists, it means the transaction won't be confirmed and the TXM needs to be restarted."+ + "Look for any error messages from previous broadcasted attempts that may indicate why this happened, i.e. wallet is out of funds. Tx: %v", tx.ID, + tx.PrintWithAttempts()) + } + + if tx.LastBroadcastAt == nil || time.Since(*tx.LastBroadcastAt) > (t.config.BlockTime*time.Duration(t.config.RetryBlockThreshold)) { + // TODO: add optional graceful bumping strategy + t.lggr.Info("Rebroadcasting attempt for txID: ", tx.ID) + return false, t.createAndSendAttempt(ctx, tx, address) + } + } + return false, nil +} + +func (t *Txm) createAndSendEmptyTx(ctx context.Context, latestNonce uint64, address common.Address) error { + tx, err := t.txStore.CreateEmptyUnconfirmedTransaction(ctx, address, latestNonce, t.config.EmptyTxLimitDefault) + if err != nil { + return err + } + return t.createAndSendAttempt(ctx, tx, address) +} + +func (t *Txm) extractMetrics(ctx context.Context, txs []*types.Transaction) []uint64 { + confirmedTxIDs := make([]uint64, 0, len(txs)) + for _, tx := range txs { + confirmedTxIDs = append(confirmedTxIDs, tx.ID) + if tx.InitialBroadcastAt != nil { + t.metrics.RecordTimeUntilTxConfirmed(ctx, float64(time.Since(*tx.InitialBroadcastAt))) + } + } + return confirmedTxIDs +} diff --git a/core/chains/evm/txm/txm_test.go b/core/chains/evm/txm/txm_test.go new file mode 100644 index 00000000000..af77f3ac084 --- /dev/null +++ b/core/chains/evm/txm/txm_test.go @@ -0,0 +1,327 @@ +package txm + +import ( + "errors" + "fmt" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + "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/testutils" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/storage" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/types" +) + +func TestLifecycle(t *testing.T) { + t.Parallel() + + client := mocks.NewClient(t) + ab := mocks.NewAttemptBuilder(t) + address1 := testutils.NewAddress() + address2 := testutils.NewAddress() + assert.NotEqual(t, address1, address2) + addresses := []common.Address{address1, address2} + keystore := mocks.NewKeystore(t) + + t.Run("retries if initial pending nonce call fails", func(t *testing.T) { + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + config := Config{BlockTime: 1 * time.Minute} + txStore := storage.NewInMemoryStoreManager(lggr, testutils.FixtureChainID) + require.NoError(t, txStore.Add(address1)) + keystore.On("EnabledAddressesForChain", mock.Anything, mock.Anything).Return([]common.Address{address1}, nil).Once() + txm := NewTxm(lggr, testutils.FixtureChainID, client, nil, txStore, nil, config, keystore) + client.On("PendingNonceAt", mock.Anything, address1).Return(uint64(0), errors.New("error")).Once() + client.On("PendingNonceAt", mock.Anything, address1).Return(uint64(100), nil).Once() + require.NoError(t, txm.Start(tests.Context(t))) + tests.AssertLogEventually(t, observedLogs, "Error when fetching initial nonce") + tests.AssertLogEventually(t, observedLogs, fmt.Sprintf("Set initial nonce for address: %v to %d", address1, 100)) + }) + + t.Run("tests lifecycle successfully without any transactions", func(t *testing.T) { + config := Config{BlockTime: 200 * time.Millisecond} + keystore.On("EnabledAddressesForChain", mock.Anything, mock.Anything).Return(addresses, nil).Once() + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + txStore := storage.NewInMemoryStoreManager(lggr, testutils.FixtureChainID) + require.NoError(t, txStore.Add(addresses...)) + txm := NewTxm(lggr, testutils.FixtureChainID, client, ab, txStore, nil, config, keystore) + var nonce uint64 + // Start + client.On("PendingNonceAt", mock.Anything, address1).Return(nonce, nil).Once() + client.On("PendingNonceAt", mock.Anything, address2).Return(nonce, nil).Once() + // backfill loop (may or may not be executed multiple times) + client.On("NonceAt", mock.Anything, address1, mock.Anything).Return(nonce, nil).Maybe() + client.On("NonceAt", mock.Anything, address2, mock.Anything).Return(nonce, nil).Maybe() + + servicetest.Run(t, txm) + tests.AssertLogEventually(t, observedLogs, "Backfill time elapsed") + }) +} + +func TestTrigger(t *testing.T) { + t.Parallel() + + address := testutils.NewAddress() + keystore := mocks.NewKeystore(t) + t.Run("Trigger fails if Txm is unstarted", func(t *testing.T) { + lggr, observedLogs := logger.TestObserved(t, zap.ErrorLevel) + txm := NewTxm(lggr, nil, nil, nil, nil, nil, Config{}, keystore) + txm.Trigger(address) + tests.AssertLogEventually(t, observedLogs, "Txm unstarted") + }) + + t.Run("executes Trigger", func(t *testing.T) { + lggr := logger.Test(t) + txStore := storage.NewInMemoryStoreManager(lggr, testutils.FixtureChainID) + require.NoError(t, txStore.Add(address)) + client := mocks.NewClient(t) + ab := mocks.NewAttemptBuilder(t) + config := Config{BlockTime: 1 * time.Minute, RetryBlockThreshold: 10} + keystore.On("EnabledAddressesForChain", mock.Anything, mock.Anything).Return([]common.Address{address}, nil) + txm := NewTxm(lggr, testutils.FixtureChainID, client, ab, txStore, nil, config, keystore) + var nonce uint64 + // Start + client.On("PendingNonceAt", mock.Anything, address).Return(nonce, nil).Maybe() + servicetest.Run(t, txm) + txm.Trigger(address) + }) +} + +func TestBroadcastTransaction(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + client := mocks.NewClient(t) + ab := mocks.NewAttemptBuilder(t) + config := Config{} + address := testutils.NewAddress() + keystore := mocks.NewKeystore(t) + + t.Run("fails if FetchUnconfirmedTransactionAtNonceWithCount for unconfirmed transactions fails", func(t *testing.T) { + mTxStore := mocks.NewTxStore(t) + mTxStore.On("FetchUnconfirmedTransactionAtNonceWithCount", mock.Anything, mock.Anything, mock.Anything).Return(nil, 0, errors.New("call failed")).Once() + txm := NewTxm(logger.Test(t), testutils.FixtureChainID, client, ab, mTxStore, nil, config, keystore) + bo, err := txm.broadcastTransaction(ctx, address) + require.Error(t, err) + assert.False(t, bo) + require.ErrorContains(t, err, "call failed") + }) + + t.Run("throws a warning and returns if unconfirmed transactions exceed maxInFlightTransactions", func(t *testing.T) { + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + mTxStore := mocks.NewTxStore(t) + mTxStore.On("FetchUnconfirmedTransactionAtNonceWithCount", mock.Anything, mock.Anything, mock.Anything).Return(nil, maxInFlightTransactions+1, nil).Once() + txm := NewTxm(lggr, testutils.FixtureChainID, client, ab, mTxStore, nil, config, keystore) + bo, err := txm.broadcastTransaction(ctx, address) + assert.True(t, bo) + require.NoError(t, err) + tests.AssertLogEventually(t, observedLogs, "Reached transaction limit") + }) + + t.Run("checks pending nonce if unconfirmed transactions are equal or more than maxInFlightSubset", func(t *testing.T) { + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + mTxStore := mocks.NewTxStore(t) + txm := NewTxm(lggr, testutils.FixtureChainID, client, ab, mTxStore, nil, config, keystore) + txm.setNonce(address, 1) + mTxStore.On("FetchUnconfirmedTransactionAtNonceWithCount", mock.Anything, mock.Anything, mock.Anything).Return(nil, maxInFlightSubset, nil).Twice() + + client.On("PendingNonceAt", mock.Anything, address).Return(uint64(0), nil).Once() // LocalNonce: 1, PendingNonce: 0 + bo, err := txm.broadcastTransaction(ctx, address) + assert.True(t, bo) + require.NoError(t, err) + + client.On("PendingNonceAt", mock.Anything, address).Return(uint64(1), nil).Once() // LocalNonce: 1, PendingNonce: 1 + mTxStore.On("UpdateUnstartedTransactionWithNonce", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Once() + bo, err = txm.broadcastTransaction(ctx, address) + assert.False(t, bo) + require.NoError(t, err) + tests.AssertLogCountEventually(t, observedLogs, "Reached transaction limit.", 1) + }) + + t.Run("fails if UpdateUnstartedTransactionWithNonce fails", func(t *testing.T) { + mTxStore := mocks.NewTxStore(t) + mTxStore.On("FetchUnconfirmedTransactionAtNonceWithCount", mock.Anything, mock.Anything, mock.Anything).Return(nil, 0, nil).Once() + txm := NewTxm(logger.Test(t), testutils.FixtureChainID, client, ab, mTxStore, nil, config, keystore) + mTxStore.On("UpdateUnstartedTransactionWithNonce", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("call failed")).Once() + bo, err := txm.broadcastTransaction(ctx, address) + assert.False(t, bo) + require.Error(t, err) + require.ErrorContains(t, err, "call failed") + }) + + t.Run("returns if there are no unstarted transactions", func(t *testing.T) { + lggr := logger.Test(t) + txStore := storage.NewInMemoryStoreManager(lggr, testutils.FixtureChainID) + require.NoError(t, txStore.Add(address)) + txm := NewTxm(lggr, testutils.FixtureChainID, client, ab, txStore, nil, config, keystore) + bo, err := txm.broadcastTransaction(ctx, address) + require.NoError(t, err) + assert.False(t, bo) + assert.Equal(t, uint64(0), txm.getNonce(address)) + }) + + t.Run("picks a new tx and creates a new attempt then sends it and updates the broadcast time", func(t *testing.T) { + lggr := logger.Test(t) + txStore := storage.NewInMemoryStoreManager(lggr, testutils.FixtureChainID) + require.NoError(t, txStore.Add(address)) + txm := NewTxm(lggr, testutils.FixtureChainID, client, ab, txStore, nil, config, keystore) + txm.setNonce(address, 8) + metrics, err := NewTxmMetrics(testutils.FixtureChainID) + require.NoError(t, err) + txm.metrics = metrics + IDK := "IDK" + txRequest := &types.TxRequest{ + Data: []byte{100, 200}, + IdempotencyKey: &IDK, + ChainID: testutils.FixtureChainID, + FromAddress: address, + ToAddress: testutils.NewAddress(), + SpecifiedGasLimit: 22000, + } + tx, err := txm.CreateTransaction(tests.Context(t), txRequest) + require.NoError(t, err) + attempt := &types.Attempt{ + TxID: tx.ID, + Fee: gas.EvmFee{GasPrice: assets.NewWeiI(1)}, + GasLimit: 22000, + } + ab.On("NewAttempt", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(attempt, nil).Once() + client.On("SendTransaction", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + + bo, err := txm.broadcastTransaction(ctx, address) + require.NoError(t, err) + assert.False(t, bo) + assert.Equal(t, uint64(9), txm.getNonce(address)) + tx, err = txStore.FindTxWithIdempotencyKey(tests.Context(t), IDK) + require.NoError(t, err) + assert.Len(t, tx.Attempts, 1) + var zeroTime time.Time + assert.Greater(t, *tx.LastBroadcastAt, zeroTime) + assert.Greater(t, *tx.Attempts[0].BroadcastAt, zeroTime) + assert.Greater(t, *tx.InitialBroadcastAt, zeroTime) + }) +} + +func TestBackfillTransactions(t *testing.T) { + t.Parallel() + + ctx := tests.Context(t) + client := mocks.NewClient(t) + ab := mocks.NewAttemptBuilder(t) + txStore := mocks.NewTxStore(t) + config := Config{} + address := testutils.NewAddress() + keystore := mocks.NewKeystore(t) + + t.Run("fails if latest nonce fetching fails", func(t *testing.T) { + txm := NewTxm(logger.Test(t), testutils.FixtureChainID, client, ab, txStore, nil, config, keystore) + client.On("NonceAt", mock.Anything, address, mock.Anything).Return(uint64(0), errors.New("latest nonce fail")).Once() + bo, err := txm.backfillTransactions(ctx, address) + require.Error(t, err) + assert.False(t, bo) + require.ErrorContains(t, err, "latest nonce fail") + }) + + t.Run("fails if MarkConfirmedAndReorgedTransactions fails", func(t *testing.T) { + txm := NewTxm(logger.Test(t), testutils.FixtureChainID, client, ab, txStore, nil, config, keystore) + client.On("NonceAt", mock.Anything, address, mock.Anything).Return(uint64(0), nil).Once() + txStore.On("MarkConfirmedAndReorgedTransactions", mock.Anything, mock.Anything, address). + Return([]*types.Transaction{}, []uint64{}, errors.New("marking transactions confirmed failed")).Once() + bo, err := txm.backfillTransactions(ctx, address) + require.Error(t, err) + assert.False(t, bo) + require.ErrorContains(t, err, "marking transactions confirmed failed") + }) + + t.Run("fills nonce gap", func(t *testing.T) { + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + txStore := storage.NewInMemoryStoreManager(lggr, testutils.FixtureChainID) + require.NoError(t, txStore.Add(address)) + ab := mocks.NewAttemptBuilder(t) + c := Config{EIP1559: false, BlockTime: 10 * time.Minute, RetryBlockThreshold: 10, EmptyTxLimitDefault: 22000} + txm := NewTxm(lggr, testutils.FixtureChainID, client, ab, txStore, nil, c, keystore) + emptyMetrics, err := NewTxmMetrics(testutils.FixtureChainID) + require.NoError(t, err) + txm.metrics = emptyMetrics + + // Add a new transaction that will be assigned with nonce = 1. Nonce = 0 is not being tracked by the txStore. This will trigger a nonce gap. + txRequest := &types.TxRequest{ + ChainID: testutils.FixtureChainID, + FromAddress: address, + ToAddress: testutils.NewAddress(), + } + _, err = txm.CreateTransaction(tests.Context(t), txRequest) + require.NoError(t, err) + _, err = txStore.UpdateUnstartedTransactionWithNonce(tests.Context(t), address, 1) // Create nonce gap + require.NoError(t, err) + + // During backfill we observe nonce has changed. The transaction with nonce = 1 should be marked unconfirmed. + // For nonce = 0 there are no transactions stored in txStore, which results in a nonce gap. + // TXM creates a new empty transaction and fills the gap. + client.On("NonceAt", mock.Anything, address, mock.Anything).Return(uint64(0), nil).Once() + attempt := &types.Attempt{ + TxID: 1, + Fee: gas.EvmFee{GasPrice: assets.NewWeiI(1)}, + GasLimit: 22000, + } + ab.On("NewAttempt", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(attempt, nil).Once() + client.On("SendTransaction", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + bo, err := txm.backfillTransactions(ctx, address) + require.NoError(t, err) + assert.False(t, bo) + tests.AssertLogEventually(t, observedLogs, fmt.Sprintf("Nonce gap at nonce: %d - address: %v. Creating a new transaction", 0, address)) + _, count, err := txStore.FetchUnconfirmedTransactionAtNonceWithCount(ctx, 0, address) + require.NoError(t, err) + assert.Equal(t, 2, count) + }) + + t.Run("retries attempt after threshold", func(t *testing.T) { + lggr, observedLogs := logger.TestObserved(t, zap.DebugLevel) + txStore := storage.NewInMemoryStoreManager(lggr, testutils.FixtureChainID) + require.NoError(t, txStore.Add(address)) + ab := mocks.NewAttemptBuilder(t) + c := Config{EIP1559: false, BlockTime: 1 * time.Second, RetryBlockThreshold: 1, EmptyTxLimitDefault: 22000} + txm := NewTxm(lggr, testutils.FixtureChainID, client, ab, txStore, nil, c, keystore) + emptyMetrics, err := NewTxmMetrics(testutils.FixtureChainID) + require.NoError(t, err) + txm.metrics = emptyMetrics + + IDK := "IDK" + txRequest := &types.TxRequest{ + Data: []byte{100, 200}, + IdempotencyKey: &IDK, + ChainID: testutils.FixtureChainID, + FromAddress: address, + ToAddress: testutils.NewAddress(), + SpecifiedGasLimit: 22000, + } + tx, err := txm.CreateTransaction(tests.Context(t), txRequest) + require.NoError(t, err) + _, err = txStore.UpdateUnstartedTransactionWithNonce(tests.Context(t), address, 0) + require.NoError(t, err) + + attempt := &types.Attempt{ + TxID: tx.ID, + Fee: gas.EvmFee{GasPrice: assets.NewWeiI(1)}, + GasLimit: 22000, + } + ab.On("NewAttempt", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(attempt, nil).Once() + + client.On("NonceAt", mock.Anything, address, mock.Anything).Return(uint64(0), nil).Once() + client.On("SendTransaction", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once() + _, err = txm.backfillTransactions(tests.Context(t), address) + require.NoError(t, err) + tests.AssertLogEventually(t, observedLogs, fmt.Sprintf("Rebroadcasting attempt for txID: %d", attempt.TxID)) + }) +} diff --git a/core/chains/evm/txm/types/transaction.go b/core/chains/evm/txm/types/transaction.go new file mode 100644 index 00000000000..1d1104545d4 --- /dev/null +++ b/core/chains/evm/txm/types/transaction.go @@ -0,0 +1,192 @@ +package types + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "math/big" + "time" + + "github.com/google/uuid" + "gopkg.in/guregu/null.v4" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + clnull "github.com/smartcontractkit/chainlink-common/pkg/utils/null" + + commontypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" +) + +type Transaction struct { + ID uint64 + IdempotencyKey *string + ChainID *big.Int + Nonce *uint64 + FromAddress common.Address + ToAddress common.Address + Value *big.Int + Data []byte + SpecifiedGasLimit uint64 + + CreatedAt time.Time + InitialBroadcastAt *time.Time + LastBroadcastAt *time.Time + + State commontypes.TxState + IsPurgeable bool + Attempts []*Attempt + AttemptCount uint16 // AttempCount is strictly kept in memory and prevents indefinite retrying + Meta *sqlutil.JSON + Subject uuid.NullUUID + + // Pipeline variables - if you aren't calling this from chain tx task within + // the pipeline, you don't need these variables + PipelineTaskRunID uuid.NullUUID + MinConfirmations clnull.Uint32 + SignalCallback bool + CallbackCompleted bool +} + +func (t *Transaction) String() string { + return fmt.Sprintf(`{txID:%d, IdempotencyKey:%v, ChainID:%v, Nonce:%s, FromAddress:%v, ToAddress:%v, Value:%v, `+ + `Data:%s, SpecifiedGasLimit:%d, CreatedAt:%v, InitialBroadcastAt:%v, LastBroadcastAt:%v, State:%v, IsPurgeable:%v, AttemptCount:%d, `+ + `Meta:%v, Subject:%v}`, + t.ID, stringOrNull(t.IdempotencyKey), t.ChainID, stringOrNull(t.Nonce), t.FromAddress, t.ToAddress, t.Value, + base64.StdEncoding.EncodeToString(t.Data), t.SpecifiedGasLimit, t.CreatedAt, stringOrNull(t.InitialBroadcastAt), stringOrNull(t.LastBroadcastAt), + t.State, t.IsPurgeable, t.AttemptCount, t.Meta, t.Subject) +} + +func stringOrNull[T any](t *T) string { + if t != nil { + return fmt.Sprintf("%v", *t) + } + return "null" +} + +func (t *Transaction) PrintWithAttempts() string { + attempts := " Attempts: [" + for _, a := range t.Attempts { + attempts += a.String() + ", " + } + attempts += "]" + + return t.String() + attempts +} + +func (t *Transaction) FindAttemptByHash(attemptHash common.Hash) (*Attempt, error) { + for _, a := range t.Attempts { + if a.Hash == attemptHash { + return a, nil + } + } + return nil, fmt.Errorf("attempt with hash: %v was not found", attemptHash) +} + +func (t *Transaction) DeepCopy() *Transaction { + txCopy := *t + attemptsCopy := make([]*Attempt, 0, len(t.Attempts)) + for _, attempt := range t.Attempts { + attemptsCopy = append(attemptsCopy, attempt.DeepCopy()) + } + txCopy.Attempts = attemptsCopy + return &txCopy +} + +func (t *Transaction) GetMeta() (*TxMeta, error) { + if t.Meta == nil { + return nil, nil + } + var m TxMeta + if err := json.Unmarshal(*t.Meta, &m); err != nil { + return nil, fmt.Errorf("unmarshalling meta: %w", err) + } + return &m, nil +} + +type Attempt struct { + ID uint64 + TxID uint64 + Hash common.Hash + Fee gas.EvmFee + GasLimit uint64 + Type byte + SignedTransaction *types.Transaction + + CreatedAt time.Time + BroadcastAt *time.Time +} + +func (a *Attempt) DeepCopy() *Attempt { + txCopy := *a + if a.SignedTransaction != nil { + txCopy.SignedTransaction = a.SignedTransaction.WithoutBlobTxSidecar() + } + return &txCopy +} + +func (a *Attempt) String() string { + return fmt.Sprintf(`{ID:%d, TxID:%d, Hash:%v, Fee:%v, GasLimit:%d, Type:%v, CreatedAt:%v, BroadcastAt:%v}`, + a.ID, a.TxID, a.Hash, a.Fee, a.GasLimit, a.Type, a.CreatedAt, stringOrNull(a.BroadcastAt)) +} + +type TxRequest struct { + IdempotencyKey *string + ChainID *big.Int + FromAddress common.Address + ToAddress common.Address + Value *big.Int + Data []byte + SpecifiedGasLimit uint64 + + Meta *sqlutil.JSON // TODO: *TxMeta after migration + ForwarderAddress common.Address + + // Pipeline variables - if you aren't calling this from chain tx task within + // the pipeline, you don't need these variables + PipelineTaskRunID uuid.NullUUID + MinConfirmations clnull.Uint32 + SignalCallback bool +} + +type TxMeta struct { + // Pipeline + JobID *int32 `json:"JobID,omitempty"` + FailOnRevert null.Bool `json:"FailOnRevert,omitempty"` + + // VRF + RequestID *common.Hash `json:"RequestID,omitempty"` + RequestTxHash *common.Hash `json:"RequestTxHash,omitempty"` + RequestIDs []common.Hash `json:"RequestIDs,omitempty"` + RequestTxHashes []common.Hash `json:"RequestTxHashes,omitempty"` + MaxLink *string `json:"MaxLink,omitempty"` + SubID *uint64 `json:"SubId,omitempty"` + GlobalSubID *string `json:"GlobalSubId,omitempty"` + MaxEth *string `json:"MaxEth,omitempty"` + ForceFulfilled *bool `json:"ForceFulfilled,omitempty"` + ForceFulfillmentAttempt *uint64 `json:"ForceFulfillmentAttempt,omitempty"` + + // Used for keepers + UpkeepID *string `json:"UpkeepID,omitempty"` + + // Used for Keystone Workflows + WorkflowExecutionID *string `json:"WorkflowExecutionID,omitempty"` + + // Forwarders + FwdrDestAddress *common.Address `json:"ForwarderDestAddress,omitempty"` + + // CCIP + MessageIDs []string `json:"MessageIDs,omitempty"` + SeqNumbers []uint64 `json:"SeqNumbers,omitempty"` + + // Dual Broadcast + DualBroadcast *bool `json:"DualBroadcast,omitempty"` + DualBroadcastParams *string `json:"DualBroadcastParams,omitempty"` +} + +type QueueingTxStrategy struct { + QueueSize uint32 + Subject uuid.NullUUID +} diff --git a/core/chains/evm/txmgr/builder.go b/core/chains/evm/txmgr/builder.go index 73c5614aba3..4a09d16d214 100644 --- a/core/chains/evm/txmgr/builder.go +++ b/core/chains/evm/txmgr/builder.go @@ -18,6 +18,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/keystore" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/clientwrappers" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txm/storage" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) @@ -91,6 +94,56 @@ func NewEvmTxm( return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, broadcaster, confirmer, resender, tracker, finalizer, client.NewTxError) } +func NewTxmV2( + ds sqlutil.DataSource, + chainConfig ChainConfig, + fCfg FeeConfig, + txConfig config.Transactions, + txmV2Config config.TransactionManagerV2, + client client.Client, + lggr logger.Logger, + logPoller logpoller.LogPoller, + keyStore keystore.Eth, + estimator gas.EvmFeeEstimator, +) (TxManager, error) { + var fwdMgr *forwarders.FwdMgr + if txConfig.ForwardersEnabled() { + fwdMgr = forwarders.NewFwdMgr(ds, client, logPoller, lggr, chainConfig) + } else { + lggr.Info("ForwarderManager: Disabled") + } + + chainID := client.ConfiguredChainID() + + var stuckTxDetector txm.StuckTxDetector + if txConfig.AutoPurge().Enabled() { + stuckTxDetectorConfig := txm.StuckTxDetectorConfig{ + BlockTime: *txmV2Config.BlockTime(), + StuckTxBlockThreshold: *txConfig.AutoPurge().Threshold(), + DetectionURL: txConfig.AutoPurge().DetectionApiUrl().String(), + } + stuckTxDetector = txm.NewStuckTxDetector(lggr, chainConfig.ChainType(), stuckTxDetectorConfig) + } + + attemptBuilder := txm.NewAttemptBuilder(chainID, fCfg.PriceMaxKey, estimator, keyStore) + inMemoryStoreManager := storage.NewInMemoryStoreManager(lggr, chainID) + config := txm.Config{ + EIP1559: fCfg.EIP1559DynamicFees(), + BlockTime: *txmV2Config.BlockTime(), + //nolint:gosec // reuse existing config until migration + RetryBlockThreshold: uint16(fCfg.BumpThreshold()), + EmptyTxLimitDefault: fCfg.LimitDefault(), + } + var c txm.Client + if txmV2Config.DualBroadcast() != nil && *txmV2Config.DualBroadcast() { + c = clientwrappers.NewDualBroadcastClient(client, keyStore, txmV2Config.CustomURL()) + } else { + c = clientwrappers.NewChainClient(client) + } + t := txm.NewTxm(lggr, chainID, c, attemptBuilder, inMemoryStoreManager, stuckTxDetector, config, keyStore) + return txm.NewTxmOrchestrator(lggr, chainID, t, inMemoryStoreManager, fwdMgr, keyStore, attemptBuilder), nil +} + // NewEvmResender creates a new concrete EvmResender func NewEvmResender( lggr logger.Logger, diff --git a/core/chains/legacyevm/evm_txm.go b/core/chains/legacyevm/evm_txm.go index 17dbce79e84..15731790f67 100644 --- a/core/chains/legacyevm/evm_txm.go +++ b/core/chains/legacyevm/evm_txm.go @@ -40,20 +40,35 @@ func newEvmTxm( ) if opts.GenTxManager == nil { - txm, err = txmgr.NewTxm( - ds, - cfg, - txmgr.NewEvmTxmFeeConfig(cfg.GasEstimator()), - cfg.Transactions(), - cfg.NodePool().Errors(), - databaseConfig, - listenerConfig, - client, - lggr, - logPoller, - opts.KeyStore, - estimator, - headTracker) + if cfg.Transactions().TransactionManagerV2().Enabled() { + txm, err = txmgr.NewTxmV2( + ds, + cfg, + txmgr.NewEvmTxmFeeConfig(cfg.GasEstimator()), + cfg.Transactions(), + cfg.Transactions().TransactionManagerV2(), + client, + lggr, + logPoller, + opts.KeyStore, + estimator, + ) + } else { + txm, err = txmgr.NewTxm( + ds, + cfg, + txmgr.NewEvmTxmFeeConfig(cfg.GasEstimator()), + cfg.Transactions(), + cfg.NodePool().Errors(), + databaseConfig, + listenerConfig, + client, + lggr, + logPoller, + opts.KeyStore, + estimator, + headTracker) + } } else { txm = opts.GenTxManager(chainID) } diff --git a/core/config/docs/chains-evm.toml b/core/config/docs/chains-evm.toml index e8adb3d611c..bdcb0e8c5d4 100644 --- a/core/config/docs/chains-evm.toml +++ b/core/config/docs/chains-evm.toml @@ -141,6 +141,16 @@ Threshold = 5 # Example # MinAttempts configures the minimum number of broadcasted attempts a transaction has to have before it is evaluated further for being terminally stuck. This threshold is only applied if there is no custom API to identify stuck transactions provided by the chain. Ensure the gas estimator configs take more bump attempts before reaching the configured max gas price. MinAttempts = 3 # Example +[EVM.Transactions.TransactionManagerV2] +# Enabled enables TransactionManagerV2. +Enabled = false # Default +# BlockTime controls the frequency of the backfill loop of TransactionManagerV2. +BlockTime = '10s' # Example +# CustomURL configures the base url of a custom endpoint used by the ChainDualBroadcast chain type. +CustomURL = 'https://example.api.io' # Example +# DualBroadcast enables DualBroadcast functionality. +DualBroadcast = false # Example + [EVM.BalanceMonitor] # Enabled balance monitoring for all keys. Enabled = true # Default diff --git a/core/config/docs/docs_test.go b/core/config/docs/docs_test.go index 9fca08ee99b..bc171caf8c1 100644 --- a/core/config/docs/docs_test.go +++ b/core/config/docs/docs_test.go @@ -97,6 +97,11 @@ func TestDoc(t *testing.T) { docDefaults.Transactions.AutoPurge.Threshold = nil docDefaults.Transactions.AutoPurge.MinAttempts = nil + // TransactionManagerV2 configs are only set if the feature is enabled + docDefaults.Transactions.TransactionManagerV2.BlockTime = nil + docDefaults.Transactions.TransactionManagerV2.CustomURL = nil + docDefaults.Transactions.TransactionManagerV2.DualBroadcast = nil + // Fallback DA oracle is not set docDefaults.GasEstimator.DAOracle = evmcfg.DAOracle{} diff --git a/core/gethwrappers/ccip/generated/fee_quoter/fee_quoter.go b/core/gethwrappers/ccip/generated/fee_quoter/fee_quoter.go index c2804d46a32..a8ee1906951 100644 --- a/core/gethwrappers/ccip/generated/fee_quoter/fee_quoter.go +++ b/core/gethwrappers/ccip/generated/fee_quoter/fee_quoter.go @@ -158,8 +158,8 @@ type KeystoneFeedsPermissionHandlerPermission struct { } var FeeQuoterMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"staticConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.StaticConfig\",\"components\":[{\"name\":\"maxFeeJuelsPerMsg\",\"type\":\"uint96\",\"internalType\":\"uint96\"},{\"name\":\"linkToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"priceUpdaters\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"feeTokens\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"tokenPriceFeeds\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenPriceFeedUpdate[]\",\"components\":[{\"name\":\"sourceToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"feedConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenPriceFeedConfig\",\"components\":[{\"name\":\"dataFeedAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenDecimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}]},{\"name\":\"tokenTransferFeeConfigArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"tokenTransferFeeConfigs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigSingleTokenArgs[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenTransferFeeConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfig\",\"components\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"deciBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}]}]},{\"name\":\"premiumMultiplierWeiPerEthArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.PremiumMultiplierWeiPerEthArgs[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"premiumMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]},{\"name\":\"destChainConfigArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.DestChainConfigArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"destChainConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"FEE_BASE_DECIMALS\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"KEYSTONE_PRICE_DECIMALS\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyAuthorizedCallerUpdates\",\"inputs\":[{\"name\":\"authorizedCallerArgs\",\"type\":\"tuple\",\"internalType\":\"structAuthorizedCallers.AuthorizedCallerArgs\",\"components\":[{\"name\":\"addedCallers\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"removedCallers\",\"type\":\"address[]\",\"internalType\":\"address[]\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyDestChainConfigUpdates\",\"inputs\":[{\"name\":\"destChainConfigArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.DestChainConfigArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"destChainConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyFeeTokensUpdates\",\"inputs\":[{\"name\":\"feeTokensToRemove\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"feeTokensToAdd\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyPremiumMultiplierWeiPerEthUpdates\",\"inputs\":[{\"name\":\"premiumMultiplierWeiPerEthArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.PremiumMultiplierWeiPerEthArgs[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"premiumMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyTokenTransferFeeConfigUpdates\",\"inputs\":[{\"name\":\"tokenTransferFeeConfigArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"tokenTransferFeeConfigs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigSingleTokenArgs[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenTransferFeeConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfig\",\"components\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"deciBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}]}]},{\"name\":\"tokensToUseDefaultFeeConfigs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigRemoveArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"convertTokenAmount\",\"inputs\":[{\"name\":\"fromToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"fromTokenAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"toToken\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAllAuthorizedCallers\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getDestChainConfig\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getDestinationChainGasPrice\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structInternal.TimestampedPackedUint224\",\"components\":[{\"name\":\"value\",\"type\":\"uint224\",\"internalType\":\"uint224\"},{\"name\":\"timestamp\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getFeeTokens\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getPremiumMultiplierWeiPerEth\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"premiumMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getStaticConfig\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.StaticConfig\",\"components\":[{\"name\":\"maxFeeJuelsPerMsg\",\"type\":\"uint96\",\"internalType\":\"uint96\"},{\"name\":\"linkToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenAndGasPrices\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[{\"name\":\"tokenPrice\",\"type\":\"uint224\",\"internalType\":\"uint224\"},{\"name\":\"gasPriceValue\",\"type\":\"uint224\",\"internalType\":\"uint224\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenPrice\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structInternal.TimestampedPackedUint224\",\"components\":[{\"name\":\"value\",\"type\":\"uint224\",\"internalType\":\"uint224\"},{\"name\":\"timestamp\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenPriceFeedConfig\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenPriceFeedConfig\",\"components\":[{\"name\":\"dataFeedAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenDecimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenPrices\",\"inputs\":[{\"name\":\"tokens\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple[]\",\"internalType\":\"structInternal.TimestampedPackedUint224[]\",\"components\":[{\"name\":\"value\",\"type\":\"uint224\",\"internalType\":\"uint224\"},{\"name\":\"timestamp\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenTransferFeeConfig\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"tokenTransferFeeConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfig\",\"components\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"deciBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatedFee\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"message\",\"type\":\"tuple\",\"internalType\":\"structClient.EVM2AnyMessage\",\"components\":[{\"name\":\"receiver\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"tokenAmounts\",\"type\":\"tuple[]\",\"internalType\":\"structClient.EVMTokenAmount[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"feeToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"extraArgs\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[{\"name\":\"feeTokenAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatedTokenPrice\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint224\",\"internalType\":\"uint224\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"onReport\",\"inputs\":[{\"name\":\"metadata\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"report\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"processMessageArgs\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"feeToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"feeTokenAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"extraArgs\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"onRampTokenTransfers\",\"type\":\"tuple[]\",\"internalType\":\"structInternal.EVM2AnyTokenTransfer[]\",\"components\":[{\"name\":\"sourcePoolAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"destTokenAddress\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"extraData\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"destExecData\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"sourceTokenAmounts\",\"type\":\"tuple[]\",\"internalType\":\"structClient.EVMTokenAmount[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}],\"outputs\":[{\"name\":\"msgFeeJuels\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"isOutOfOrderExecution\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"convertedExtraArgs\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"destExecDataPerToken\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setReportPermissions\",\"inputs\":[{\"name\":\"permissions\",\"type\":\"tuple[]\",\"internalType\":\"structKeystoneFeedsPermissionHandler.Permission[]\",\"components\":[{\"name\":\"forwarder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"workflowName\",\"type\":\"bytes10\",\"internalType\":\"bytes10\"},{\"name\":\"reportName\",\"type\":\"bytes2\",\"internalType\":\"bytes2\"},{\"name\":\"workflowOwner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"isAllowed\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"typeAndVersion\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"updatePrices\",\"inputs\":[{\"name\":\"priceUpdates\",\"type\":\"tuple\",\"internalType\":\"structInternal.PriceUpdates\",\"components\":[{\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\",\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"components\":[{\"name\":\"sourceToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"usdPerToken\",\"type\":\"uint224\",\"internalType\":\"uint224\"}]},{\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\",\"internalType\":\"structInternal.GasPriceUpdate[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"usdPerUnitGas\",\"type\":\"uint224\",\"internalType\":\"uint224\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateTokenPriceFeeds\",\"inputs\":[{\"name\":\"tokenPriceFeedUpdates\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenPriceFeedUpdate[]\",\"components\":[{\"name\":\"sourceToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"feedConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenPriceFeedConfig\",\"components\":[{\"name\":\"dataFeedAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenDecimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"AuthorizedCallerAdded\",\"inputs\":[{\"name\":\"caller\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AuthorizedCallerRemoved\",\"inputs\":[{\"name\":\"caller\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"DestChainAdded\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"destChainConfig\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"DestChainConfigUpdated\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"destChainConfig\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FeeTokenAdded\",\"inputs\":[{\"name\":\"feeToken\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FeeTokenRemoved\",\"inputs\":[{\"name\":\"feeToken\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferRequested\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"PremiumMultiplierWeiPerEthUpdated\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"premiumMultiplierWeiPerEth\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"PriceFeedPerTokenUpdated\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"priceFeedConfig\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structFeeQuoter.TokenPriceFeedConfig\",\"components\":[{\"name\":\"dataFeedAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenDecimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ReportPermissionSet\",\"inputs\":[{\"name\":\"reportId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"permission\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structKeystoneFeedsPermissionHandler.Permission\",\"components\":[{\"name\":\"forwarder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"workflowName\",\"type\":\"bytes10\",\"internalType\":\"bytes10\"},{\"name\":\"reportName\",\"type\":\"bytes2\",\"internalType\":\"bytes2\"},{\"name\":\"workflowOwner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"isAllowed\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TokenTransferFeeConfigDeleted\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TokenTransferFeeConfigUpdated\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"tokenTransferFeeConfig\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structFeeQuoter.TokenTransferFeeConfig\",\"components\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"deciBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UsdPerTokenUpdated\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"timestamp\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UsdPerUnitGasUpdated\",\"inputs\":[{\"name\":\"destChain\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"timestamp\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"CannotTransferToSelf\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"DataFeedValueOutOfUint224Range\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"DestinationChainNotEnabled\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]},{\"type\":\"error\",\"name\":\"ExtraArgOutOfOrderExecutionMustBeTrue\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FeeTokenNotSupported\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"InvalidDestBytesOverhead\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"type\":\"error\",\"name\":\"InvalidDestChainConfig\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]},{\"type\":\"error\",\"name\":\"InvalidEVMAddress\",\"inputs\":[{\"name\":\"encodedAddress\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidExtraArgsTag\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidFeeRange\",\"inputs\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidStaticConfig\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MessageFeeTooHigh\",\"inputs\":[{\"name\":\"msgFeeJuels\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxFeeJuelsPerMsg\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"MessageGasLimitTooHigh\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MessageTooLarge\",\"inputs\":[{\"name\":\"maxSize\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actualSize\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"MustBeProposedOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"OnlyCallableByOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"OwnerCannotBeZero\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ReportForwarderUnauthorized\",\"inputs\":[{\"name\":\"forwarder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"workflowOwner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"workflowName\",\"type\":\"bytes10\",\"internalType\":\"bytes10\"},{\"name\":\"reportName\",\"type\":\"bytes2\",\"internalType\":\"bytes2\"}]},{\"type\":\"error\",\"name\":\"SourceTokenDataTooLarge\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"StaleGasPrice\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"threshold\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"timePassed\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"TokenNotSupported\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"UnauthorizedCaller\",\"inputs\":[{\"name\":\"caller\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"UnsupportedNumberOfTokens\",\"inputs\":[{\"name\":\"numberOfTokens\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ZeroAddressNotAllowed\",\"inputs\":[]}]", - Bin: "0x60e0604052346110505761755180380380610019816112bc565b9283398101908082036101208112611050576060136110505761003a61127e565b81516001600160601b038116810361105057815261005a602083016112e1565b906020810191825261006e604084016112f5565b6040820190815260608401516001600160401b038111611050578561009491860161131d565b60808501519094906001600160401b03811161105057866100b691830161131d565b60a08201519096906001600160401b0381116110505782019080601f830112156110505781516100ed6100e882611306565b6112bc565b9260208085848152019260071b8201019083821161105057602001915b8183106112095750505060c08301516001600160401b0381116110505783019781601f8a011215611050578851986101446100e88b611306565b996020808c838152019160051b830101918483116110505760208101915b8383106110a7575050505060e08401516001600160401b0381116110505784019382601f8601121561105057845161019c6100e882611306565b9560208088848152019260061b8201019085821161105057602001915b81831061106b57505050610100810151906001600160401b038211611050570182601f82011215611050578051906101f36100e883611306565b93602061028081878681520194028301019181831161105057602001925b828410610e8e57505050503315610e7d57600180546001600160a01b031916331790556020986102408a6112bc565b9760008952600036813761025261129d565b998a52888b8b015260005b89518110156102c4576001906001600160a01b0361027b828d6113b6565b51168d610287826115a2565b610294575b50500161025d565b7fc3803387881faad271c47728894e3e36fac830ffc8602ca6fc07733cbda7758091604051908152a1388d61028c565b508a985089519660005b885181101561033f576001600160a01b036102e9828b6113b6565b511690811561032e577feb1b9b92e50b7f88f9ff25d56765095ac6e91540eee214906f4036a908ffbdef8c8361032060019561152a565b50604051908152a1016102ce565b6342bcdf7f60e11b60005260046000fd5b5081518a985089906001600160a01b0316158015610e6b575b8015610e5c575b610e4b5791516001600160a01b031660a05290516001600160601b03166080525163ffffffff1660c052610392866112bc565b9360008552600036813760005b855181101561040e576001906103c76001600160a01b036103c0838a6113b6565b5116611437565b6103d2575b0161039f565b818060a01b036103e282896113b6565b51167f1795838dc8ab2ffc5f431a1729a6afa0b587f982f7b2be0b9d7187a1ef547f91600080a26103cc565b508694508560005b84518110156104855760019061043e6001600160a01b0361043783896113b6565b5116611569565b610449575b01610416565b818060a01b0361045982886113b6565b51167fdf1b1bd32a69711488d71554706bb130b1fc63a5fa1a2cd85e8440f84065ba23600080a2610443565b508593508460005b835181101561054757806104a3600192866113b6565b517fe6a7a17d710bf0b2cd05e5397dc6f97a5da4ee79e31e234bf5f965ee2bd9a5bf606089858060a01b038451169301518360005260078b5260406000209060ff878060a01b038251169283898060a01b03198254161781558d8301908151604082549501948460a81b8651151560a81b16918560a01b9060a01b169061ffff60a01b19161717905560405193845251168c8301525115156040820152a20161048d565b5091509160005b8251811015610ae35761056181846113b6565b51856001600160401b0361057584876113b6565b5151169101519080158015610ad0575b8015610ab0575b8015610a92575b610a7e57600081815260098852604090205460019392919060081b6001600160e01b03191661093657807f71e9302ab4e912a9678ae7f5a8542856706806f2817e1bf2a20b171e265cb4ad604051806106fc868291909161024063ffffffff8161026084019580511515855261ffff602082015116602086015282604082015116604086015282606082015116606086015282608082015116608086015260ff60a08201511660a086015260ff60c08201511660c086015261ffff60e08201511660e0860152826101008201511661010086015261ffff6101208201511661012086015261ffff610140820151166101408601528260e01b61016082015116610160860152610180810151151561018086015261ffff6101a0820151166101a0860152826101c0820151166101c0860152826101e0820151166101e086015260018060401b03610200820151166102008601528261022082015116610220860152015116910152565b0390a25b60005260098752826040600020825115158382549162ffff008c83015160081b169066ffffffff000000604084015160181b166affffffff00000000000000606085015160381b16926effffffff0000000000000000000000608086015160581b169260ff60781b60a087015160781b169460ff60801b60c088015160801b169161ffff60881b60e089015160881b169063ffffffff60981b6101008a015160981b169361ffff60b81b6101208b015160b81b169661ffff60c81b6101408c015160c81b169963ffffffff60d81b6101608d015160081c169b61018060ff60f81b910151151560f81b169c8f8060f81b039a63ffffffff60d81b199961ffff60c81b199861ffff60b81b199763ffffffff60981b199661ffff60881b199560ff60801b199460ff60781b19936effffffff0000000000000000000000199260ff6affffffff000000000000001992169066ffffffffffffff19161716171617161716171617161716171617161716179063ffffffff60d81b1617178155019061ffff6101a0820151169082549165ffffffff00006101c083015160101b169269ffffffff0000000000006101e084015160301b166a01000000000000000000008860901b0361020085015160501b169263ffffffff60901b61022086015160901b169461024063ffffffff60b01b91015160b01b169563ffffffff60b01b199363ffffffff60901b19926a01000000000000000000008c60901b0319918c8060501b03191617161716171617171790550161054e565b807f2431cc0363f2f66b21782c7e3d54dd9085927981a21bd0cc6be45a51b19689e360405180610a76868291909161024063ffffffff8161026084019580511515855261ffff602082015116602086015282604082015116604086015282606082015116606086015282608082015116608086015260ff60a08201511660a086015260ff60c08201511660c086015261ffff60e08201511660e0860152826101008201511661010086015261ffff6101208201511661012086015261ffff610140820151166101408601528260e01b61016082015116610160860152610180810151151561018086015261ffff6101a0820151166101a0860152826101c0820151166101c0860152826101e0820151166101e086015260018060401b03610200820151166102008601528261022082015116610220860152015116910152565b0390a2610700565b63c35aa79d60e01b60005260045260246000fd5b5063ffffffff6101e08301511663ffffffff60608401511610610593565b506101608201516001600160e01b031916630a04b54b60e21b141561058c565b5063ffffffff6101e08301511615610585565b84828560005b8151811015610b69576001906001600160a01b03610b0782856113b6565b5151167fbb77da6f7210cdd16904228a9360133d1d7dfff99b1bc75f128da5b53e28f97d86848060401b0381610b3d86896113b6565b510151168360005260088252604060002081878060401b0319825416179055604051908152a201610ae9565b83600184610b76836112bc565b9060008252600092610e46575b909282935b8251851015610d8557610b9b85846113b6565b5180516001600160401b0316939083019190855b83518051821015610d7457610bc58287926113b6565b51015184516001600160a01b0390610bde9084906113b6565b5151169063ffffffff815116908781019163ffffffff8351169081811015610d5f5750506080810163ffffffff815116898110610d48575090899291838c52600a8a5260408c20600160a01b6001900386168d528a5260408c2092825163ffffffff169380549180518d1b67ffffffff0000000016916040860192835160401b69ffff000000000000000016966060810195865160501b6dffffffff00000000000000000000169063ffffffff60701b895160701b169260a001998b60ff60901b8c51151560901b169560ff60901b199363ffffffff60701b19926dffffffff000000000000000000001991600160501b60019003191617161716171617171790556040519586525163ffffffff168c8601525161ffff1660408501525163ffffffff1660608401525163ffffffff16608083015251151560a082015260c07f94967ae9ea7729ad4f54021c1981765d2b1d954f7c92fbec340aa0a54f46b8b591a3600101610baf565b6312766e0160e11b8c52600485905260245260448bfd5b6305a7b3d160e11b8c5260045260245260448afd5b505060019096019593509050610b88565b9150825b8251811015610e07576001906001600160401b03610da782866113b6565b515116828060a01b0384610dbb84886113b6565b5101511690808752600a855260408720848060a01b038316885285528660408120557f4de5b1bcbca6018c11303a2c3f4a4b4f22a1c741d8c4ba430d246ac06c5ddf8b8780a301610d89565b604051615f1a908161163782396080518181816104d40152613273015260a051818181610517015261320a015260c05181818161053e0152613f1b0152f35b610b83565b63d794ef9560e01b60005260046000fd5b5063ffffffff8251161561035f565b5080516001600160601b031615610358565b639b15e16f60e01b60005260046000fd5b838203610280811261105057610260610ea561129d565b91610eaf87611393565b8352601f190112611050576040519161026083016001600160401b0381118482101761105557604052610ee460208701611386565b8352610ef2604087016113a7565b6020840152610f03606087016112f5565b6040840152610f14608087016112f5565b6060840152610f2560a087016112f5565b6080840152610f3660c08701611378565b60a0840152610f4760e08701611378565b60c0840152610f5961010087016113a7565b60e0840152610f6b61012087016112f5565b610100840152610f7e61014087016113a7565b610120840152610f9161016087016113a7565b610140840152610180860151916001600160e01b0319831683036110505783602093610160610280960152610fc96101a08901611386565b610180820152610fdc6101c089016113a7565b6101a0820152610fef6101e089016112f5565b6101c082015261100261020089016112f5565b6101e08201526110156102208901611393565b61020082015261102861024089016112f5565b61022082015261103b61026089016112f5565b61024082015283820152815201930192610211565b600080fd5b634e487b7160e01b600052604160045260246000fd5b60408387031261105057602060409161108261129d565b61108b866112e1565b8152611098838701611393565b838201528152019201916101b9565b82516001600160401b0381116110505782016040818803601f190112611050576110cf61129d565b906110dc60208201611393565b825260408101516001600160401b03811161105057602091010187601f8201121561105057805161110f6100e882611306565b91602060e08185858152019302820101908a821161105057602001915b81831061114b5750505091816020938480940152815201920191610162565b828b0360e081126110505760c061116061129d565b9161116a866112e1565b8352601f190112611050576040519160c08301916001600160401b038311848410176110555760e0936020936040526111a48488016112f5565b81526111b2604088016112f5565b848201526111c2606088016113a7565b60408201526111d3608088016112f5565b60608201526111e460a088016112f5565b60808201526111f560c08801611386565b60a08201528382015281520192019161112c565b8284036080811261105057606061121e61129d565b91611228866112e1565b8352601f1901126110505760809160209161124161127e565b61124c8488016112e1565b815261125a60408801611378565b8482015261126a60608801611386565b60408201528382015281520192019161010a565b60405190606082016001600160401b0381118382101761105557604052565b60408051919082016001600160401b0381118382101761105557604052565b6040519190601f01601f191682016001600160401b0381118382101761105557604052565b51906001600160a01b038216820361105057565b519063ffffffff8216820361105057565b6001600160401b0381116110555760051b60200190565b9080601f830112156110505781516113376100e882611306565b9260208085848152019260051b82010192831161105057602001905b8282106113605750505090565b6020809161136d846112e1565b815201910190611353565b519060ff8216820361105057565b5190811515820361105057565b51906001600160401b038216820361105057565b519061ffff8216820361105057565b80518210156113ca5760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b80548210156113ca5760005260206000200190600090565b8054801561142157600019019061140f82826113e0565b8154906000199060031b1b1916905555565b634e487b7160e01b600052603160045260246000fd5b6000818152600c602052604090205480156114f85760001981018181116114e257600b546000198101919082116114e257818103611491575b50505061147d600b6113f8565b600052600c60205260006040812055600190565b6114ca6114a26114b393600b6113e0565b90549060031b1c928392600b6113e0565b819391549060031b91821b91600019901b19161790565b9055600052600c602052604060002055388080611470565b634e487b7160e01b600052601160045260246000fd5b5050600090565b8054906801000000000000000082101561105557816114b3916001611526940181556113e0565b9055565b806000526003602052604060002054156000146115635761154c8160026114ff565b600254906000526003602052604060002055600190565b50600090565b80600052600c602052604060002054156000146115635761158b81600b6114ff565b600b5490600052600c602052604060002055600190565b60008181526003602052604090205480156114f85760001981018181116114e2576002546000198101919082116114e2578082036115fc575b5050506115e860026113f8565b600052600360205260006040812055600190565b61161e61160d6114b39360026113e0565b90549060031b1c92839260026113e0565b905560005260036020526040600020553880806115db56fe6080604052600436101561001257600080fd5b60003560e01c806241e5be1461020657806301ffc9a714610201578063061877e3146101fc57806306285c69146101f7578063181f5a77146101f25780632451a627146101ed578063325c868e146101e85780633937306f146101e357806341ed29e7146101de578063430d138c146101d957806345ac924d146101d45780634ab35b0b146101cf578063514e8cff146101ca5780636def4ce7146101c5578063770e2dc4146101c057806379ba5097146101bb5780637afac322146101b6578063805f2132146101b157806382b49eb0146101ac57806387b8d879146101a75780638da5cb5b146101a257806391a2749a1461019d578063a69c64c014610198578063bf78e03f14610193578063cdc73d511461018e578063d02641a014610189578063d63d3af214610184578063d8694ccd1461017f578063f2fde38b1461017a578063fbe3f778146101755763ffdb4b371461017057600080fd5b612d38565b612bff565b612b0b565b6125f3565b6125b9565b61253d565b6124a8565b6123bd565b6122e6565b612216565b6121c4565b611f6c565b611dc4565b611a83565b611912565b6117c2565b611567565b6113ca565b6111ab565b611140565b61103b565b610edb565b610c44565b610885565b61084b565b6107aa565b6106d9565b61047a565b610407565b6102c5565b61023b565b73ffffffffffffffffffffffffffffffffffffffff81160361022957565b600080fd5b35906102398261020b565b565b346102295760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957602061029060043561027b8161020b565b6024356044359161028b8361020b565b612eed565b604051908152f35b35907fffffffff000000000000000000000000000000000000000000000000000000008216820361022957565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610229576004357fffffffff0000000000000000000000000000000000000000000000000000000081168103610229577fffffffff00000000000000000000000000000000000000000000000000000000602091167f805f21320000000000000000000000000000000000000000000000000000000081149081156103dd575b81156103b3575b8115610389575b506040519015158152f35b7f01ffc9a7000000000000000000000000000000000000000000000000000000009150143861037e565b7f181f5a770000000000000000000000000000000000000000000000000000000081149150610377565b7f9b645f410000000000000000000000000000000000000000000000000000000081149150610370565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295773ffffffffffffffffffffffffffffffffffffffff6004356104578161020b565b166000526008602052602067ffffffffffffffff60406000205416604051908152f35b346102295760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610229576104b1612f36565b5060606040516104c0816105a5565b63ffffffff6bffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169182815273ffffffffffffffffffffffffffffffffffffffff60406020830192827f00000000000000000000000000000000000000000000000000000000000000001684520191837f00000000000000000000000000000000000000000000000000000000000000001683526040519485525116602084015251166040820152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176105c157604052565b610576565b60a0810190811067ffffffffffffffff8211176105c157604052565b6040810190811067ffffffffffffffff8211176105c157604052565b60c0810190811067ffffffffffffffff8211176105c157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176105c157604052565b6040519061023960408361061a565b604051906102396102608361061a565b919082519283825260005b8481106106c45750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b80602080928401015182828601015201610685565b346102295760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957610756604080519061071a818361061a565b601382527f46656551756f74657220312e362e302d6465760000000000000000000000000060208301525191829160208352602083019061067a565b0390f35b602060408183019282815284518094520192019060005b81811061077e5750505090565b825173ffffffffffffffffffffffffffffffffffffffff16845260209384019390920191600101610771565b346102295760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760405180602060025491828152019060026000527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace9060005b81811061083557610756856108298187038261061a565b6040519182918261075a565b8254845260209093019260019283019201610812565b346102295760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957602060405160248152f35b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff811161022957806004019060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8236030112610229576108ff6142e6565b6109098280612f55565b4263ffffffff1692915060005b818110610ae35750506024019061092d8284612f55565b92905060005b83811061093c57005b8061095b610956600193610950868a612f55565b90612fd8565b61304d565b7fdd84a3fa9ef9409f550d54d6affec7e9c480c878c6ab27b78912a03e1b371c6e67ffffffffffffffff610aaa610a876020850194610a796109b987517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b6109e86109c461065b565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092168252565b63ffffffff8c166020820152610a23610a09845167ffffffffffffffff1690565b67ffffffffffffffff166000526005602052604060002090565b815160209092015160e01b7fffffffff00000000000000000000000000000000000000000000000000000000167bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216919091179055565b5167ffffffffffffffff1690565b93517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b604080517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9290921682524260208301529190931692a201610933565b80610afc610af76001936109508980612f55565b613016565b7f52f50aa6d1a95a4595361ecf953d095f125d442e4673716dede699e049de148a73ffffffffffffffffffffffffffffffffffffffff610bde610a876020850194610bc4610b6687517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b610b716109c461065b565b63ffffffff8d166020820152610a23610b9e845173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff166000526006602052604060002090565b5173ffffffffffffffffffffffffffffffffffffffff1690565b604080517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9290921682524260208301529190931692a201610916565b67ffffffffffffffff81116105c15760051b60200190565b8015150361022957565b359061023982610c2f565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff8111610229573660238201121561022957806004013590610c9f82610c17565b90610cad604051928361061a565b828252602460a06020840194028201019036821161022957602401925b818410610cdc57610cda83613072565b005b60a0843603126102295760405190610cf3826105c6565b8435610cfe8161020b565b825260208501357fffffffffffffffffffff00000000000000000000000000000000000000000000811681036102295760208301526040850135907fffff000000000000000000000000000000000000000000000000000000000000821682036102295782602092604060a0950152610d796060880161022e565b6060820152610d8a60808801610c39565b6080820152815201930192610cca565b6004359067ffffffffffffffff8216820361022957565b6024359067ffffffffffffffff8216820361022957565b359067ffffffffffffffff8216820361022957565b9181601f840112156102295782359167ffffffffffffffff8311610229576020838186019501011161022957565b9181601f840112156102295782359167ffffffffffffffff8311610229576020808501948460051b01011161022957565b929091610e5d9284521515602084015260806040840152608083019061067a565b906060818303910152815180825260208201916020808360051b8301019401926000915b838310610e9057505050505090565b9091929394602080610ecc837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08660019603018752895161067a565b97019301930191939290610e81565b346102295760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957610f12610d9a565b60243590610f1f8261020b565b60443560643567ffffffffffffffff811161022957610f42903690600401610ddd565b60849391933567ffffffffffffffff811161022957610f65903690600401610e0b565b9160a4359567ffffffffffffffff871161022957366023880112156102295786600401359567ffffffffffffffff8711610229573660248860061b8a01011161022957610756986024610fb99901966131fe565b9060409492945194859485610e3c565b602060408183019282815284518094520192019060005b818110610fed5750505090565b9091926020604082611030600194885163ffffffff602080927bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8151168552015116910152565b019401929101610fe0565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff81116102295761108a903690600401610e0b565b61109381610c17565b916110a1604051938461061a565b8183527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06110ce83610c17565b0160005b81811061112957505060005b8281101561111b576001906110ff6110fa8260051b8501613357565b613eba565b61110982876131ea565b5261111481866131ea565b50016110de565b604051806107568682610fc9565b60209061113461333e565b828288010152016110d2565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760206111856004356111808161020b565b614237565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff60405191168152f35b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295767ffffffffffffffff6111eb610d9a565b6111f361333e565b5016600052600560205260406000206040519061120f826105e2565b547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116825260e01c6020820152604051809161075682604081019263ffffffff602080927bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8151168552015116910152565b610239909291926102408061026083019561129784825115159052565b60208181015161ffff169085015260408181015163ffffffff169085015260608181015163ffffffff169085015260808181015163ffffffff169085015260a08181015160ff169085015260c08181015160ff169085015260e08181015161ffff16908501526101008181015163ffffffff16908501526101208181015161ffff16908501526101408181015161ffff1690850152610160818101517fffffffff000000000000000000000000000000000000000000000000000000001690850152610180818101511515908501526101a08181015161ffff16908501526101c08181015163ffffffff16908501526101e08181015163ffffffff16908501526102008181015167ffffffffffffffff16908501526102208181015163ffffffff1690850152015163ffffffff16910152565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610229576107566114ab6114a661140a610d9a565b600061024061141761066a565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e08201528261020082015282610220820152015267ffffffffffffffff166000526009602052604060002090565b61339b565b6040519182918261127a565b359063ffffffff8216820361022957565b359061ffff8216820361022957565b81601f82011215610229578035906114ee82610c17565b926114fc604051948561061a565b82845260208085019360061b8301019181831161022957602001925b828410611526575050505090565b6040848303126102295760206040918251611540816105e2565b61154987610dc8565b8152828701356115588161020b565b83820152815201930192611518565b346102295760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff811161022957366023820112156102295780600401356115c181610c17565b916115cf604051938461061a565b8183526024602084019260051b820101903682116102295760248101925b82841061161e576024358567ffffffffffffffff821161022957611618610cda9236906004016114d7565b90613509565b833567ffffffffffffffff811161022957820160407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc8236030112610229576040519061166a826105e2565b61167660248201610dc8565b8252604481013567ffffffffffffffff811161022957602491010136601f820112156102295780356116a781610c17565b916116b5604051938461061a565b818352602060e081850193028201019036821161022957602001915b8183106116f057505050918160209384809401528152019301926115ed565b82360360e081126102295760c07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06040519261172b846105e2565b86356117368161020b565b845201126102295760e091602091604051611750816105fe565b61175b8488016114b7565b8152611769604088016114b7565b84820152611779606088016114c8565b604082015261178a608088016114b7565b606082015261179b60a088016114b7565b608082015260c08701356117ae81610c2f565b60a0820152838201528152019201916116d1565b346102295760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760005473ffffffffffffffffffffffffffffffffffffffff81163303611881577fffffffffffffffffffffffff00000000000000000000000000000000000000006001549133828416176001551660005573ffffffffffffffffffffffffffffffffffffffff3391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f02b543c60000000000000000000000000000000000000000000000000000000060005260046000fd5b9080601f830112156102295781356118c281610c17565b926118d0604051948561061a565b81845260208085019260051b82010192831161022957602001905b8282106118f85750505090565b6020809183356119078161020b565b8152019101906118eb565b346102295760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff8111610229576119619036906004016118ab565b60243567ffffffffffffffff8111610229576119819036906004016118ab565b9061198a61432a565b60005b8151811015611a0657806119ae6119a9610bc4600194866131ea565b615b0b565b6119b9575b0161198d565b73ffffffffffffffffffffffffffffffffffffffff6119db610bc483866131ea565b167f1795838dc8ab2ffc5f431a1729a6afa0b587f982f7b2be0b9d7187a1ef547f91600080a26119b3565b8260005b8151811015610cda5780611a2b611a26610bc4600194866131ea565b615b2c565b611a36575b01611a0a565b73ffffffffffffffffffffffffffffffffffffffff611a58610bc483866131ea565b167fdf1b1bd32a69711488d71554706bb130b1fc63a5fa1a2cd85e8440f84065ba23600080a2611a30565b346102295760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff811161022957611ad2903690600401610ddd565b6024359167ffffffffffffffff831161022957611b2b611b23611b09611aff611b33963690600401610ddd565b9490953691613811565b90604082015190605e604a84015160601c93015191929190565b9190336149c8565b810190613876565b60005b8151811015610cda57611b98611b93611b6d611b5284866131ea565b515173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b613935565b611bac611ba86040830151151590565b1590565b611d6e5790611c21611bc46020600194015160ff1690565b611c1b611bfa6020611bd686896131ea565b5101517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b90614aa7565b611c3c6040611c3084876131ea565b51015163ffffffff1690565b63ffffffff611c67611c5e611c57610b9e611b52888b6131ea565b5460e01c90565b63ffffffff1690565b911610611d6857611cca611c806040611c3085886131ea565b611cba611c8b61065b565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff851681529163ffffffff166020830152565b610a23610b9e611b5286896131ea565b7f52f50aa6d1a95a4595361ecf953d095f125d442e4673716dede699e049de148a73ffffffffffffffffffffffffffffffffffffffff611d0d611b5285886131ea565b611d5e611d1f6040611c30888b6131ea565b60405193849316958390929163ffffffff6020917bffffffffffffffffffffffffffffffffffffffffffffffffffffffff604085019616845216910152565b0390a25b01611b36565b50611d62565b611dc0611d7e611b5284866131ea565b7f06439c6b0000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff16600452602490565b6000fd5b346102295760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957610756611e77611e01610d9a565b67ffffffffffffffff60243591611e178361020b565b600060a0604051611e27816105fe565b828152826020820152826040820152826060820152826080820152015216600052600a60205260406000209073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b611ef3611eea60405192611e8a846105fe565b5463ffffffff8116845263ffffffff8160201c16602085015261ffff8160401c166040850152611ed1611ec48263ffffffff9060501c1690565b63ffffffff166060860152565b63ffffffff607082901c16608085015260901c60ff1690565b151560a0830152565b6040519182918291909160a08060c083019463ffffffff815116845263ffffffff602082015116602085015261ffff604082015116604085015263ffffffff606082015116606085015263ffffffff608082015116608085015201511515910152565b60ff81160361022957565b359061023982611f56565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff8111610229573660238201121561022957806004013590611fc782610c17565b90611fd5604051928361061a565b82825260246102806020840194028201019036821161022957602401925b81841061200357610cda836139d2565b8336036102808112610229576102607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060405192612040846105e2565b61204988610dc8565b84520112610229576102809160209161206061066a565b61206b848901610c39565b8152612079604089016114c8565b84820152612089606089016114b7565b604082015261209a608089016114b7565b60608201526120ab60a089016114b7565b60808201526120bc60c08901611f61565b60a08201526120cd60e08901611f61565b60c08201526120df61010089016114c8565b60e08201526120f161012089016114b7565b61010082015261210461014089016114c8565b61012082015261211761016089016114c8565b61014082015261212a6101808901610298565b61016082015261213d6101a08901610c39565b6101808201526121506101c089016114c8565b6101a08201526121636101e089016114b7565b6101c082015261217661020089016114b7565b6101e08201526121896102208901610dc8565b61020082015261219c61024089016114b7565b6102208201526121af61026089016114b7565b61024082015283820152815201930192611ff3565b346102295760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957602073ffffffffffffffffffffffffffffffffffffffff60015416604051908152f35b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff81116102295760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82360301126102295760405161228f816105e2565b816004013567ffffffffffffffff8111610229576122b390600436918501016118ab565b8152602482013567ffffffffffffffff811161022957610cda9260046122dc92369201016118ab565b6020820152613c31565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff811161022957366023820112156102295780600401359061234182610c17565b9061234f604051928361061a565b8282526024602083019360061b8201019036821161022957602401925b81841061237c57610cda83613dd1565b6040843603126102295760206040918251612396816105e2565b86356123a18161020b565b81526123ae838801610dc8565b8382015281520193019261236c565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295773ffffffffffffffffffffffffffffffffffffffff60043561240d8161020b565b612415612f36565b50166000526007602052610756604060002060ff60405191612436836105a5565b5473ffffffffffffffffffffffffffffffffffffffff81168352818160a01c16602084015260a81c161515604082015260405191829182919091604080606083019473ffffffffffffffffffffffffffffffffffffffff815116845260ff602082015116602085015201511515910152565b346102295760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957604051806020600b54918281520190600b6000527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db99060005b81811061252757610756856108298187038261061a565b8254845260209093019260019283019201612510565b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957604061257d6004356110fa8161020b565b6125b78251809263ffffffff602080927bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8151168552015116910152565bf35b346102295760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957602060405160128152f35b346102295760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295761262a610d9a565b6024359067ffffffffffffffff821161022957816004019160a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc82360301126102295761268f6114a68367ffffffffffffffff166000526009602052604060002090565b61269c611ba88251151590565b612ad35760648201916126e0611ba86126b485613357565b73ffffffffffffffffffffffffffffffffffffffff166000526001600b01602052604060002054151590565b612a85576044810190856126f48382612f55565b9690506024830195612727612709888561400a565b905089612720612719878061400a565b3691613811565b9189615435565b8561273461118083613357565b998a9361275261274c61022085015163ffffffff1690565b826154f5565b976000808d15612a4c5750506127b761ffff856127e9986127c39896612804966127f7966127ae61279e6101c06127926101a061280a9f015161ffff1690565b97015163ffffffff1690565b916127a88c613357565b94612f55565b96909516615610565b97919796909794613357565b73ffffffffffffffffffffffffffffffffffffffff166000526008602052604060002090565b5467ffffffffffffffff1690565b67ffffffffffffffff1690565b90612ea1565b9660009861ffff6128216101408a015161ffff1690565b166129f2575b50956128046127f761020061295b61296b996dffffffffffffffffffffffffffff6107569f9d986129739f9b61294c7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9f6129138e6129069f61290b6129069f978f956129539963ffffffff61289e6128a89360849761400a565b929050169061405b565b9060a08701876128cb6128c56128bf845160ff1690565b60ff1690565b85612ea1565b9360e08201926128dd845161ffff1690565b9061ffff82168311612983575b5050506080015161290692611c5e92509063ffffffff16614099565b61405b565b95019061400a565b6101e083015163ffffffff169063ffffffff61294461018061293c606088015163ffffffff1690565b960151151590565b941692615918565b519061405b565b911690612ea1565b93015167ffffffffffffffff1690565b911690612eb4565b6040519081529081906020820190565b6129069596506129e6936128046128bf60c06129d6611c5e99976129d06129c96129c061ffff9a60ff6129ba6129df9c5160ff1690565b16614068565b9a5161ffff1690565b61ffff1690565b90613ead565b93015160ff1690565b911661405b565b929150873880806128ea565b82879b999493969a50612a188993986dffffffffffffffffffffffffffff9060701c1690565b6dffffffffffffffffffffffffffff1691612a33898861400a565b9050612a3f9385615873565b9894919297999590612827565b959350955050506128046127f76127e96127c3612a7f612a7a611c5e61024061280a99015163ffffffff1690565b612e5a565b94613357565b611dc0612a9184613357565b7f2502348c0000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff16600452602490565b7f99ac52f20000000000000000000000000000000000000000000000000000000060005267ffffffffffffffff831660045260246000fd5b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295773ffffffffffffffffffffffffffffffffffffffff600435612b5b8161020b565b612b6361432a565b16338114612bd557807fffffffffffffffffffffffff0000000000000000000000000000000000000000600054161760005573ffffffffffffffffffffffffffffffffffffffff600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278600080a3005b7fdad89dca0000000000000000000000000000000000000000000000000000000060005260046000fd5b346102295760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126102295760043567ffffffffffffffff8111610229573660238201121561022957806004013590612c5a82610c17565b90612c68604051928361061a565b8282526024602083019360071b8201019036821161022957602401925b818410612c9557610cda836140b3565b833603608081126102295760607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe060405192612cd0846105e2565b8735612cdb8161020b565b8452011261022957608091602091604051612cf5816105a5565b83880135612d028161020b565b81526040880135612d1281611f56565b848201526060880135612d2481610c2f565b604082015283820152815201930192612c85565b346102295760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261022957600435612d738161020b565b612d7b610db1565b9067ffffffffffffffff82169182600052600960205260ff6040600020541615612dfd57612dab612dcc92614237565b92600052600960205263ffffffff60016040600020015460901c16906154f5565b604080517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9384168152919092166020820152f35b827f99ac52f20000000000000000000000000000000000000000000000000000000060005260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b90662386f26fc10000820291808304662386f26fc100001490151715612e7c57565b612e2b565b90655af3107a4000820291808304655af3107a40001490151715612e7c57565b81810292918115918404141715612e7c57565b8115612ebe570490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b612f2c612f26612f3394937bffffffffffffffffffffffffffffffffffffffffffffffffffffffff612f1f8195614237565b1690612ea1565b92614237565b1690612eb4565b90565b60405190612f43826105a5565b60006040838281528260208201520152565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610229570180359067ffffffffffffffff821161022957602001918160061b3603831361022957565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9190811015612fe85760061b0190565b612fa9565b35907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8216820361022957565b60408136031261022957613045602060405192613032846105e2565b803561303d8161020b565b845201612fed565b602082015290565b60408136031261022957613045602060405192613069846105e2565b61303d81610dc8565b9061307b61432a565b60005b82518110156131e55780613094600192856131ea565b517f32a4ba3fa3351b11ad555d4c8ec70a744e8705607077a946807030d64b6ab1a360a073ffffffffffffffffffffffffffffffffffffffff83511692606081019373ffffffffffffffffffffffffffffffffffffffff80865116957fffff00000000000000000000000000000000000000000000000000000000000061314c60208601947fffffffffffffffffffff00000000000000000000000000000000000000000000865116604088019a848c5116926159aa565b977fffffffffffffffffffff0000000000000000000000000000000000000000000060808701956131b8875115158c600052600460205260406000209060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b8560405198511688525116602087015251166040850152511660608301525115156080820152a20161307e565b509050565b8051821015612fe85760209160051b010190565b989592919097949693977f00000000000000000000000000000000000000000000000000000000000000009073ffffffffffffffffffffffffffffffffffffffff821673ffffffffffffffffffffffffffffffffffffffff82161460001461332e575050965b6bffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168089116132fd5750916132dc6132f094926132f6969463ffffffff8060016132cf8f67ffffffffffffffff166000526009602052604060002090565b015460301c16169161444e565b966132ea6020890151151590565b99614616565b9261482a565b9293929190565b887f6a92a4830000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b9161333892612eed565b96613264565b6040519061334b826105e2565b60006020838281520152565b35612f338161020b565b9060405161336e816105e2565b91547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116835260e01c6020830152565b906102396134fb60016133ac61066a565b9461349a61349082546133c86133c28260ff1690565b15158a52565b61ffff600882901c1660208a015263ffffffff601882901c1660408a015263ffffffff603882901c1660608a015263ffffffff605882901c1660808a015260ff607882901c1660a08a015260ff608082901c1660c08a015261ffff608882901c1660e08a015263ffffffff609882901c166101008a015261ffff60b882901c166101208a015261ffff60c882901c166101408a01527fffffffff00000000000000000000000000000000000000000000000000000000600882901b166101608a015260f81c90565b1515610180880152565b015461ffff81166101a086015263ffffffff601082901c166101c086015263ffffffff603082901c166101e086015267ffffffffffffffff605082901c1661020086015263ffffffff609082901c1661022086015260b01c63ffffffff1690565b63ffffffff16610240840152565b9061351261432a565b6000915b80518310156137435761352983826131ea565b519061353d825167ffffffffffffffff1690565b946020600093019367ffffffffffffffff8716935b8551805182101561372e57613569826020926131ea565b51015161357a611b528389516131ea565b8151602083015163ffffffff9081169116818110156136f5575050608082015163ffffffff16602081106136a7575090867f94967ae9ea7729ad4f54021c1981765d2b1d954f7c92fbec340aa0a54f46b8b573ffffffffffffffffffffffffffffffffffffffff84613636858f6001999861360c6136319267ffffffffffffffff16600052600a602052604060002090565b9073ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b614874565b61369e60405192839216958291909160a08060c083019463ffffffff815116845263ffffffff602082015116602085015261ffff604082015116604085015263ffffffff606082015116606085015263ffffffff608082015116608085015201511515910152565b0390a301613552565b7f24ecdc020000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff90911660045263ffffffff1660245260446000fd5b7f0b4f67a20000000000000000000000000000000000000000000000000000000060005263ffffffff9081166004521660245260446000fd5b50509550925092600191500191929092613516565b50905060005b815181101561380d5780613771613762600193856131ea565b515167ffffffffffffffff1690565b67ffffffffffffffff73ffffffffffffffffffffffffffffffffffffffff6137ba602061379e86896131ea565b51015173ffffffffffffffffffffffffffffffffffffffff1690565b60006137de8261360c8767ffffffffffffffff16600052600a602052604060002090565b551691167f4de5b1bcbca6018c11303a2c3f4a4b4f22a1c741d8c4ba430d246ac06c5ddf8b600080a301613749565b5050565b92919267ffffffffffffffff82116105c15760405191613859601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0166020018461061a565b829481845281830111610229578281602093846000960137010152565b6020818303126102295780359067ffffffffffffffff8211610229570181601f82011215610229578035906138aa82610c17565b926138b8604051948561061a565b8284526020606081860194028301019181831161022957602001925b8284106138e2575050505090565b6060848303126102295760206060916040516138fd816105a5565b86356139088161020b565b8152613915838801612fed565b83820152613925604088016114b7565b60408201528152019301926138d4565b90604051613942816105a5565b604060ff82945473ffffffffffffffffffffffffffffffffffffffff81168452818160a01c16602085015260a81c161515910152565b90610239604051613988816105fe565b925463ffffffff8082168552602082811c821690860152604082811c61ffff1690860152605082901c81166060860152607082901c16608085015260901c60ff16151560a0840152565b906139db61432a565b60005b82518110156131e5576139f181846131ea565b516020613a0161376284876131ea565b9101519067ffffffffffffffff811680158015613c12575b8015613b99575b8015613b6b575b613b335791613af98260019594613aa9613a84613a5b613afe9767ffffffffffffffff166000526009602052604060002090565b5460081b7fffffffff000000000000000000000000000000000000000000000000000000001690565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b613b04577f71e9302ab4e912a9678ae7f5a8542856706806f2817e1bf2a20b171e265cb4ad60405180613adc878261127a565b0390a267ffffffffffffffff166000526009602052604060002090565b614bde565b016139de565b7f2431cc0363f2f66b21782c7e3d54dd9085927981a21bd0cc6be45a51b19689e360405180613adc878261127a565b7fc35aa79d0000000000000000000000000000000000000000000000000000000060005267ffffffffffffffff821660045260246000fd5b506101e083015163ffffffff1663ffffffff613b91611c5e606087015163ffffffff1690565b911611613a27565b507f2812d52c000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000613c0a6101608601517fffffffff000000000000000000000000000000000000000000000000000000001690565b161415613a20565b5063ffffffff613c2a6101e085015163ffffffff1690565b1615613a19565b613c3961432a565b60208101519160005b8351811015613ced5780613c5b610bc4600193876131ea565b613c97613c9273ffffffffffffffffffffffffffffffffffffffff83165b73ffffffffffffffffffffffffffffffffffffffff1690565b615e46565b613ca3575b5001613c42565b60405173ffffffffffffffffffffffffffffffffffffffff9190911681527fc3803387881faad271c47728894e3e36fac830ffc8602ca6fc07733cbda7758090602090a138613c9c565b5091505160005b815181101561380d57613d0a610bc482846131ea565b9073ffffffffffffffffffffffffffffffffffffffff821615613da7577feb1b9b92e50b7f88f9ff25d56765095ac6e91540eee214906f4036a908ffbdef613d9e83613d76613d71613c7960019773ffffffffffffffffffffffffffffffffffffffff1690565b615dcd565b5060405173ffffffffffffffffffffffffffffffffffffffff90911681529081906020820190565b0390a101613cf4565b7f8579befe0000000000000000000000000000000000000000000000000000000060005260046000fd5b613dd961432a565b60005b815181101561380d578073ffffffffffffffffffffffffffffffffffffffff613e07600193856131ea565b5151167fbb77da6f7210cdd16904228a9360133d1d7dfff99b1bc75f128da5b53e28f97d613ea467ffffffffffffffff6020613e4386896131ea565b51015116836000526008602052604060002067ffffffffffffffff82167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000008254161790556040519182918291909167ffffffffffffffff6020820193169052565b0390a201613ddc565b91908203918211612e7c57565b613ec261333e565b50613ef5613ef08273ffffffffffffffffffffffffffffffffffffffff166000526006602052604060002090565b613361565b6020810191613f14613f0e611c5e855163ffffffff1690565b42613ead565b63ffffffff7f00000000000000000000000000000000000000000000000000000000000000001611613fc957611b93613f6d9173ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b613f7d611ba86040830151151590565b8015613fcf575b613fc957613f91906152ac565b9163ffffffff613fb9611c5e613fae602087015163ffffffff1690565b935163ffffffff1690565b911610613fc4575090565b905090565b50905090565b5073ffffffffffffffffffffffffffffffffffffffff614003825173ffffffffffffffffffffffffffffffffffffffff1690565b1615613f84565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610229570180359067ffffffffffffffff82116102295760200191813603831361022957565b91908201809211612e7c57565b9061ffff8091169116029061ffff8216918203612e7c57565b63ffffffff60209116019063ffffffff8211612e7c57565b9063ffffffff8091169116019063ffffffff8211612e7c57565b906140bc61432a565b60005b82518110156131e557806140d5600192856131ea565b517fe6a7a17d710bf0b2cd05e5397dc6f97a5da4ee79e31e234bf5f965ee2bd9a5bf61422e602073ffffffffffffffffffffffffffffffffffffffff845116930151836000526007602052604060002061418173ffffffffffffffffffffffffffffffffffffffff835116829073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b602082015181547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff74ff000000000000000000000000000000000000000075ff0000000000000000000000000000000000000000006040870151151560a81b169360a01b1691161717905560405191829182919091604080606083019473ffffffffffffffffffffffffffffffffffffffff815116845260ff602082015116602085015201511515910152565b0390a2016140bf565b61424081613eba565b9063ffffffff6020830151161580156142bf575b61427b5750517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff907f06439c6b000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff82511615614254565b336000526003602052604060002054156142fc57565b7fd86ad9cf000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b73ffffffffffffffffffffffffffffffffffffffff60015416330361434b57565b7f2b5c74de0000000000000000000000000000000000000000000000000000000060005260046000fd5b919091357fffffffff00000000000000000000000000000000000000000000000000000000811692600481106143a9575050565b7fffffffff00000000000000000000000000000000000000000000000000000000929350829060040360031b1b161690565b909291928360041161022957831161022957600401917ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0190565b90816020910312610229575190565b908160409103126102295760206040519161443f836105e2565b80518352015161304581610c2f565b9161445761333e565b50811561454d575061449861271982806144927fffffffff000000000000000000000000000000000000000000000000000000009587614375565b956143db565b91167f181dcf100000000000000000000000000000000000000000000000000000000081036144d5575080602080612f3393518301019101614425565b7f97a657c90000000000000000000000000000000000000000000000000000000014614525577f5247fdce0000000000000000000000000000000000000000000000000000000060005260046000fd5b8060208061453893518301019101614416565b61454061065b565b9081526000602082015290565b91505067ffffffffffffffff61456161065b565b911681526000602082015290565b9061457982610c17565b614586604051918261061a565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06145b48294610c17565b019060005b8281106145c557505050565b8060606020809385010152016145b9565b9190811015612fe85760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6181360301821215610229570190565b90929161463a613a5b8367ffffffffffffffff166000526009602052604060002090565b906146448161456f565b9560005b828110614659575050505050505090565b61466c614667828489612fd8565b613357565b838861468661467c8584846145d6565b604081019061400a565b9050602081116147a2575b5083926146c06146ba6127196146b06001986146e3976146de976145d6565b602081019061400a565b89615a3b565b61360c8967ffffffffffffffff16600052600a602052604060002090565b613978565b60a0810151156147665761474a614704606061471e93015163ffffffff1690565b6040805163ffffffff909216602083015290928391820190565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0810183528261061a565b614754828b6131ea565b5261475f818a6131ea565b5001614648565b5061471e61474a61479d8461478f8a67ffffffffffffffff166000526009602052604060002090565b015460101c63ffffffff1690565b614704565b9150506147da611c5e6147cd8461360c8b67ffffffffffffffff16600052600a602052604060002090565b5460701c63ffffffff1690565b106147e757838838614691565b7f36f536ca0000000000000000000000000000000000000000000000000000000060005273ffffffffffffffffffffffffffffffffffffffff1660045260246000fd5b6020604051917f181dcf1000000000000000000000000000000000000000000000000000000000828401528051602484015201511515604482015260448152612f3360648261061a565b815181546020808501516040808701517fffffffffffffffffffffffffffffffffffffffffffff0000000000000000000090941663ffffffff958616179190921b67ffffffff00000000161791901b69ffff0000000000000000161782556060830151610239936149849260a092614926911685547fffffffffffffffffffffffffffffffffffff00000000ffffffffffffffffffff1660509190911b6dffffffff0000000000000000000016178555565b61497d61493a608083015163ffffffff1690565b85547fffffffffffffffffffffffffffff00000000ffffffffffffffffffffffffffff1660709190911b71ffffffff000000000000000000000000000016178555565b0151151590565b81547fffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffff1690151560901b72ff00000000000000000000000000000000000016179055565b919290926149d8828286866159aa565b600052600460205260ff60406000205416156149f45750505050565b6040517f097e17ff00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff93841660048201529390921660248401527fffffffffffffffffffff0000000000000000000000000000000000000000000090911660448301527fffff000000000000000000000000000000000000000000000000000000000000166064820152608490fd5b0390fd5b604d8111612e7c57600a0a90565b60ff1660120160ff8111612e7c5760ff16906024821115614b6c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc8201918211612e7c57614af8614afe92614a99565b90612eb4565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111614b42577bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b7f10cb51d10000000000000000000000000000000000000000000000000000000060005260046000fd5b906024039060248211612e7c57612804614b8592614a99565b614afe565b9060ff80911691160160ff8111612e7c5760ff16906024821115614b6c577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc8201918211612e7c57614af8614afe92614a99565b906151f8610240600161023994614c29614bf88651151590565b829060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b614c6f614c3b602087015161ffff1690565b82547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ff1660089190911b62ffff0016178255565b614cbb614c83604087015163ffffffff1690565b82547fffffffffffffffffffffffffffffffffffffffffffffffffff00000000ffffff1660189190911b66ffffffff00000016178255565b614d0b614ccf606087015163ffffffff1690565b82547fffffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffff1660389190911b6affffffff0000000000000016178255565b614d5f614d1f608087015163ffffffff1690565b82547fffffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffff1660589190911b6effffffff000000000000000000000016178255565b614db1614d7060a087015160ff1690565b82547fffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffff1660789190911b6fff00000000000000000000000000000016178255565b614e04614dc260c087015160ff1690565b82547fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff1660809190911b70ff0000000000000000000000000000000016178255565b614e5a614e1660e087015161ffff1690565b82547fffffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffffff1660889190911b72ffff000000000000000000000000000000000016178255565b614eb7614e6f61010087015163ffffffff1690565b82547fffffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffff1660989190911b76ffffffff0000000000000000000000000000000000000016178255565b614f14614eca61012087015161ffff1690565b82547fffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffffffff1660b89190911b78ffff000000000000000000000000000000000000000000000016178255565b614f73614f2761014087015161ffff1690565b82547fffffffffff0000ffffffffffffffffffffffffffffffffffffffffffffffffff1660c89190911b7affff0000000000000000000000000000000000000000000000000016178255565b614ff4614fa46101608701517fffffffff000000000000000000000000000000000000000000000000000000001690565b82547fff00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff1660089190911c7effffffff00000000000000000000000000000000000000000000000000000016178255565b615055615005610180870151151590565b82547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690151560f81b7fff0000000000000000000000000000000000000000000000000000000000000016178255565b019261509961506a6101a083015161ffff1690565b859061ffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000825416179055565b6150e56150ae6101c083015163ffffffff1690565b85547fffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ffff1660109190911b65ffffffff000016178555565b6151356150fa6101e083015163ffffffff1690565b85547fffffffffffffffffffffffffffffffffffffffffffff00000000ffffffffffff1660309190911b69ffffffff00000000000016178555565b61519161514e61020083015167ffffffffffffffff1690565b85547fffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffff1660509190911b71ffffffffffffffff0000000000000000000016178555565b6151ed6151a661022083015163ffffffff1690565b85547fffffffffffffffffffff00000000ffffffffffffffffffffffffffffffffffff1660909190911b75ffffffff00000000000000000000000000000000000016178555565b015163ffffffff1690565b7fffffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffff79ffffffff0000000000000000000000000000000000000000000083549260b01b169116179055565b519069ffffffffffffffffffff8216820361022957565b908160a09103126102295761526d81615242565b91602082015191604081015191612f33608060608401519301615242565b6040513d6000823e3d90fd5b908160209103126102295751612f3381611f56565b6152b461333e565b506152d9613c79613c79835173ffffffffffffffffffffffffffffffffffffffff1690565b90604051907ffeaf968c00000000000000000000000000000000000000000000000000000000825260a082600481865afa9283156153f6576000926000946153fb575b5060008312614b42576020600491604051928380927f313ce5670000000000000000000000000000000000000000000000000000000082525afa9283156153f657612f339363ffffffff93615382936000926153c0575b506020015160ff165b90614b8a565b926153b261538e61065b565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff9095168552565b1663ffffffff166020830152565b61537c9192506153e7602091823d84116153ef575b6153df818361061a565b810190615297565b929150615373565b503d6153d5565b61528b565b90935061542191925060a03d60a01161542e575b615419818361061a565b810190615259565b509392505091923861531c565b503d61540f565b919063ffffffff6040840151168082116154c557505061ffff6020830151169081811161548f575050907fffffffff0000000000000000000000000000000000000000000000000000000061016061023993015116615a3b565b61ffff92507fd88dddd6000000000000000000000000000000000000000000000000000000006000526004521660245260446000fd5b7f869337890000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b67ffffffffffffffff811660005260056020526040600020916040519261551b846105e2565b547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116845260e01c9182602085015263ffffffff8216928361557f575b50505050612f3390517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b63ffffffff164290810393908411612e7c57831161559d5780615555565b7ff08bcb3e0000000000000000000000000000000000000000000000000000000060005267ffffffffffffffff1660045263ffffffff1660245260445260646000fd5b604081360312610229576020604051916155f9836105e2565b80356156048161020b565b83520135602082015290565b9694919695929390956000946000986000986000965b80881061563a575050505050505050929190565b9091929394959697999a6156576156528a848b612fd8565b6155e0565b9a6156bf6146de8d61569b6156808967ffffffffffffffff16600052600a602052604060002090565b915173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b916156d0611ba860a0850151151590565b6158405760009c60408401906156eb6129c9835161ffff1690565b6157a1575b5050606083015163ffffffff1661570691614099565b9c60808301516157199063ffffffff1690565b61572291614099565b9b82516157329063ffffffff1690565b63ffffffff1661574190612e5a565b600193908083106157955750612a7a611c5e602061576493015163ffffffff1690565b80821161578457506157759161405b565b985b0196959493929190615626565b905061578f9161405b565b98615777565b91505061578f9161405b565b90612804615831939f61581f6158289460208f8e6129c9955073ffffffffffffffffffffffffffffffffffffffff6157ed855173ffffffffffffffffffffffffffffffffffffffff1690565b911673ffffffffffffffffffffffffffffffffffffffff821614615839576158159150614237565b915b015190615b4d565b925161ffff1690565b620186a0900490565b9b38806156f0565b5091615817565b999b50600191506158678461586161586d9361585b8b612e5a565b9061405b565b9b614099565b9c614081565b9a615777565b91939093806101e00193846101e011612e7c576101208102908082046101201490151715612e7c576101e0910101809311612e7c576129c9610140615909612f33966dffffffffffffffffffffffffffff6129536158f46158e16159139a63ffffffff6128049a169061405b565b6128046129c96101208c015161ffff1690565b61585b611c5e6101008b015163ffffffff1690565b93015161ffff1690565b612e81565b9063ffffffff6159359395949561592d61333e565b50169161444e565b918251116159805780615974575b61594a5790565b7fee433e990000000000000000000000000000000000000000000000000000000060005260046000fd5b50602081015115615943565b7f4c4fc93a0000000000000000000000000000000000000000000000000000000060005260046000fd5b6040805173ffffffffffffffffffffffffffffffffffffffff9283166020820190815292909316908301527fffffffffffffffffffff0000000000000000000000000000000000000000000090921660608201527fffff000000000000000000000000000000000000000000000000000000000000909216608083015290615a358160a0810161471e565b51902090565b7fffffffff00000000000000000000000000000000000000000000000000000000167f2812d52c0000000000000000000000000000000000000000000000000000000014615a865750565b6020815103615ac957615aa26020825183010160208301614416565b73ffffffffffffffffffffffffffffffffffffffff8111908115615aff575b50615ac95750565b614a95906040519182917f8d666f6000000000000000000000000000000000000000000000000000000000835260048301615b82565b61040091501038615ac1565b73ffffffffffffffffffffffffffffffffffffffff612f339116600b615c7a565b73ffffffffffffffffffffffffffffffffffffffff612f339116600b615e08565b670de0b6b3a7640000917bffffffffffffffffffffffffffffffffffffffffffffffffffffffff615b7e9216612ea1565b0490565b906020612f3392818152019061067a565b8054821015612fe85760005260206000200190600090565b91615be3918354907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b9055565b80548015615c4b577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190615c1c8282615b93565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82549160031b1b1916905555565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b6001810191806000528260205260406000205492831515600014615d68577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8401848111612e7c578354937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8501948511612e7c576000958583615d1997615d0a9503615d1f575b505050615be7565b90600052602052604060002090565b55600190565b615d4f615d4991615d40615d36615d5f9588615b93565b90549060031b1c90565b92839187615b93565b90615bab565b8590600052602052604060002090565b55388080615d02565b50505050600090565b805490680100000000000000008210156105c15781615d98916001615be394018155615b93565b81939154907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9060031b92831b921b19161790565b600081815260036020526040902054615e0257615deb816002615d71565b600254906000526003602052604060002055600190565b50600090565b6000828152600182016020526040902054615e3f5780615e2a83600193615d71565b80549260005201602052604060002055600190565b5050600090565b600081815260036020526040902054908115615e3f577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820190828211612e7c57600254927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8401938411612e7c578383615d199460009603615ee2575b505050615ed16002615be7565b600390600052602052604060002090565b615ed1615d4991615efa615d36615f04956002615b93565b9283916002615b93565b55388080615ec456fea164736f6c634300081a000a", + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"staticConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.StaticConfig\",\"components\":[{\"name\":\"maxFeeJuelsPerMsg\",\"type\":\"uint96\",\"internalType\":\"uint96\"},{\"name\":\"linkToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"priceUpdaters\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"feeTokens\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"tokenPriceFeeds\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenPriceFeedUpdate[]\",\"components\":[{\"name\":\"sourceToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"feedConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenPriceFeedConfig\",\"components\":[{\"name\":\"dataFeedAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenDecimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}]},{\"name\":\"tokenTransferFeeConfigArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"tokenTransferFeeConfigs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigSingleTokenArgs[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenTransferFeeConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfig\",\"components\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"deciBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}]}]},{\"name\":\"premiumMultiplierWeiPerEthArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.PremiumMultiplierWeiPerEthArgs[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"premiumMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]},{\"name\":\"destChainConfigArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.DestChainConfigArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"destChainConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"FEE_BASE_DECIMALS\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"KEYSTONE_PRICE_DECIMALS\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyAuthorizedCallerUpdates\",\"inputs\":[{\"name\":\"authorizedCallerArgs\",\"type\":\"tuple\",\"internalType\":\"structAuthorizedCallers.AuthorizedCallerArgs\",\"components\":[{\"name\":\"addedCallers\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"removedCallers\",\"type\":\"address[]\",\"internalType\":\"address[]\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyDestChainConfigUpdates\",\"inputs\":[{\"name\":\"destChainConfigArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.DestChainConfigArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"destChainConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyFeeTokensUpdates\",\"inputs\":[{\"name\":\"feeTokensToRemove\",\"type\":\"address[]\",\"internalType\":\"address[]\"},{\"name\":\"feeTokensToAdd\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyPremiumMultiplierWeiPerEthUpdates\",\"inputs\":[{\"name\":\"premiumMultiplierWeiPerEthArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.PremiumMultiplierWeiPerEthArgs[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"premiumMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"applyTokenTransferFeeConfigUpdates\",\"inputs\":[{\"name\":\"tokenTransferFeeConfigArgs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"tokenTransferFeeConfigs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigSingleTokenArgs[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenTransferFeeConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfig\",\"components\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"deciBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}]}]},{\"name\":\"tokensToUseDefaultFeeConfigs\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfigRemoveArgs[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"convertTokenAmount\",\"inputs\":[{\"name\":\"fromToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"fromTokenAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"toToken\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getAllAuthorizedCallers\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getDestChainConfig\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getDestinationChainGasPrice\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structInternal.TimestampedPackedUint224\",\"components\":[{\"name\":\"value\",\"type\":\"uint224\",\"internalType\":\"uint224\"},{\"name\":\"timestamp\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getFeeTokens\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getPremiumMultiplierWeiPerEth\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"premiumMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getStaticConfig\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.StaticConfig\",\"components\":[{\"name\":\"maxFeeJuelsPerMsg\",\"type\":\"uint96\",\"internalType\":\"uint96\"},{\"name\":\"linkToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenAndGasPrices\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[{\"name\":\"tokenPrice\",\"type\":\"uint224\",\"internalType\":\"uint224\"},{\"name\":\"gasPriceValue\",\"type\":\"uint224\",\"internalType\":\"uint224\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenPrice\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structInternal.TimestampedPackedUint224\",\"components\":[{\"name\":\"value\",\"type\":\"uint224\",\"internalType\":\"uint224\"},{\"name\":\"timestamp\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenPriceFeedConfig\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenPriceFeedConfig\",\"components\":[{\"name\":\"dataFeedAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenDecimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenPrices\",\"inputs\":[{\"name\":\"tokens\",\"type\":\"address[]\",\"internalType\":\"address[]\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple[]\",\"internalType\":\"structInternal.TimestampedPackedUint224[]\",\"components\":[{\"name\":\"value\",\"type\":\"uint224\",\"internalType\":\"uint224\"},{\"name\":\"timestamp\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getTokenTransferFeeConfig\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"tokenTransferFeeConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenTransferFeeConfig\",\"components\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"deciBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatedFee\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"message\",\"type\":\"tuple\",\"internalType\":\"structClient.EVM2AnyMessage\",\"components\":[{\"name\":\"receiver\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"tokenAmounts\",\"type\":\"tuple[]\",\"internalType\":\"structClient.EVMTokenAmount[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"feeToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"extraArgs\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"outputs\":[{\"name\":\"feeTokenAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatedTokenPrice\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint224\",\"internalType\":\"uint224\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"onReport\",\"inputs\":[{\"name\":\"metadata\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"report\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"processMessageArgs\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"feeToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"feeTokenAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"extraArgs\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"onRampTokenTransfers\",\"type\":\"tuple[]\",\"internalType\":\"structInternal.EVM2AnyTokenTransfer[]\",\"components\":[{\"name\":\"sourcePoolAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"destTokenAddress\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"extraData\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"destExecData\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"sourceTokenAmounts\",\"type\":\"tuple[]\",\"internalType\":\"structClient.EVMTokenAmount[]\",\"components\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}],\"outputs\":[{\"name\":\"msgFeeJuels\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"isOutOfOrderExecution\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"convertedExtraArgs\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"destExecDataPerToken\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setReportPermissions\",\"inputs\":[{\"name\":\"permissions\",\"type\":\"tuple[]\",\"internalType\":\"structKeystoneFeedsPermissionHandler.Permission[]\",\"components\":[{\"name\":\"forwarder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"workflowName\",\"type\":\"bytes10\",\"internalType\":\"bytes10\"},{\"name\":\"reportName\",\"type\":\"bytes2\",\"internalType\":\"bytes2\"},{\"name\":\"workflowOwner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"isAllowed\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"typeAndVersion\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"updatePrices\",\"inputs\":[{\"name\":\"priceUpdates\",\"type\":\"tuple\",\"internalType\":\"structInternal.PriceUpdates\",\"components\":[{\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\",\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"components\":[{\"name\":\"sourceToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"usdPerToken\",\"type\":\"uint224\",\"internalType\":\"uint224\"}]},{\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\",\"internalType\":\"structInternal.GasPriceUpdate[]\",\"components\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"usdPerUnitGas\",\"type\":\"uint224\",\"internalType\":\"uint224\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"updateTokenPriceFeeds\",\"inputs\":[{\"name\":\"tokenPriceFeedUpdates\",\"type\":\"tuple[]\",\"internalType\":\"structFeeQuoter.TokenPriceFeedUpdate[]\",\"components\":[{\"name\":\"sourceToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"feedConfig\",\"type\":\"tuple\",\"internalType\":\"structFeeQuoter.TokenPriceFeedConfig\",\"components\":[{\"name\":\"dataFeedAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenDecimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"AuthorizedCallerAdded\",\"inputs\":[{\"name\":\"caller\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"AuthorizedCallerRemoved\",\"inputs\":[{\"name\":\"caller\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"DestChainAdded\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"destChainConfig\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"DestChainConfigUpdated\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"destChainConfig\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structFeeQuoter.DestChainConfig\",\"components\":[{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"maxDataBytes\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxPerMsgGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerPayloadByteBase\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteHigh\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"destGasPerPayloadByteThreshold\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityOverheadGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destGasPerDataAvailabilityByte\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destDataAvailabilityMultiplierBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"},{\"name\":\"enforceOutOfOrder\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"defaultTokenFeeUSDCents\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"defaultTokenDestGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"defaultTxGasLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"gasMultiplierWeiPerEth\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"gasPriceStalenessThreshold\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"networkFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FeeTokenAdded\",\"inputs\":[{\"name\":\"feeToken\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"FeeTokenRemoved\",\"inputs\":[{\"name\":\"feeToken\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferRequested\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"PremiumMultiplierWeiPerEthUpdated\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"premiumMultiplierWeiPerEth\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"PriceFeedPerTokenUpdated\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"priceFeedConfig\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structFeeQuoter.TokenPriceFeedConfig\",\"components\":[{\"name\":\"dataFeedAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenDecimals\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ReportPermissionSet\",\"inputs\":[{\"name\":\"reportId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"permission\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structKeystoneFeedsPermissionHandler.Permission\",\"components\":[{\"name\":\"forwarder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"workflowName\",\"type\":\"bytes10\",\"internalType\":\"bytes10\"},{\"name\":\"reportName\",\"type\":\"bytes2\",\"internalType\":\"bytes2\"},{\"name\":\"workflowOwner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"isAllowed\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TokenTransferFeeConfigDeleted\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TokenTransferFeeConfigUpdated\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"tokenTransferFeeConfig\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structFeeQuoter.TokenTransferFeeConfig\",\"components\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"deciBps\",\"type\":\"uint16\",\"internalType\":\"uint16\"},{\"name\":\"destGasOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"isEnabled\",\"type\":\"bool\",\"internalType\":\"bool\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UsdPerTokenUpdated\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"timestamp\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UsdPerUnitGasUpdated\",\"inputs\":[{\"name\":\"destChain\",\"type\":\"uint64\",\"indexed\":true,\"internalType\":\"uint64\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"timestamp\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"CannotTransferToSelf\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"DataFeedValueOutOfUint224Range\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"DestinationChainNotEnabled\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]},{\"type\":\"error\",\"name\":\"ExtraArgOutOfOrderExecutionMustBeTrue\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FeeTokenNotSupported\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"InvalidChainFamilySelector\",\"inputs\":[{\"name\":\"chainFamilySelector\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}]},{\"type\":\"error\",\"name\":\"InvalidDestBytesOverhead\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"destBytesOverhead\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"type\":\"error\",\"name\":\"InvalidDestChainConfig\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]},{\"type\":\"error\",\"name\":\"InvalidEVMAddress\",\"inputs\":[{\"name\":\"encodedAddress\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidExtraArgsData\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidExtraArgsTag\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidFeeRange\",\"inputs\":[{\"name\":\"minFeeUSDCents\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxFeeUSDCents\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidSVMAddress\",\"inputs\":[{\"name\":\"SVMAddress\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidStaticConfig\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidTokenReceiver\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MessageComputeUnitLimitTooHigh\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MessageFeeTooHigh\",\"inputs\":[{\"name\":\"msgFeeJuels\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxFeeJuelsPerMsg\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"MessageGasLimitTooHigh\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MessageTooLarge\",\"inputs\":[{\"name\":\"maxSize\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actualSize\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"MustBeProposedOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"OnlyCallableByOwner\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"OwnerCannotBeZero\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ReportForwarderUnauthorized\",\"inputs\":[{\"name\":\"forwarder\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"workflowOwner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"workflowName\",\"type\":\"bytes10\",\"internalType\":\"bytes10\"},{\"name\":\"reportName\",\"type\":\"bytes2\",\"internalType\":\"bytes2\"}]},{\"type\":\"error\",\"name\":\"SourceTokenDataTooLarge\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"StaleGasPrice\",\"inputs\":[{\"name\":\"destChainSelector\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"threshold\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"timePassed\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"TokenNotSupported\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"UnauthorizedCaller\",\"inputs\":[{\"name\":\"caller\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"UnsupportedNumberOfTokens\",\"inputs\":[{\"name\":\"numberOfTokens\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxNumberOfTokensPerMsg\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ZeroAddressNotAllowed\",\"inputs\":[]}]", + Bin: "0x60e060405234611029576170618038038061001981611295565b9283398101908082036101208112611029576060136110295761003a611257565b81516001600160601b038116810361102957815261005a602083016112ba565b906020810191825261006e604084016112ce565b6040820190815260608401516001600160401b03811161102957856100949186016112f6565b60808501519094906001600160401b03811161102957866100b69183016112f6565b60a08201519096906001600160401b0381116110295782019080601f830112156110295781516100ed6100e8826112df565b611295565b9260208085848152019260071b8201019083821161102957602001915b8183106111e25750505060c08301516001600160401b0381116110295783019781601f8a011215611029578851986101446100e88b6112df565b996020808c838152019160051b830101918483116110295760208101915b838310611080575050505060e08401516001600160401b0381116110295784019382601f8601121561102957845161019c6100e8826112df565b9560208088848152019260061b8201019085821161102957602001915b81831061104457505050610100810151906001600160401b038211611029570182601f82011215611029578051906101f36100e8836112df565b93602061028081878681520194028301019181831161102957602001925b828410610e6757505050503315610e5657600180546001600160a01b031916331790556020986102408a611295565b97600089526000368137610252611276565b998a52888b8b015260005b89518110156102c4576001906001600160a01b0361027b828d61138f565b51168d6102878261157b565b610294575b50500161025d565b7fc3803387881faad271c47728894e3e36fac830ffc8602ca6fc07733cbda7758091604051908152a1388d61028c565b508a985089519660005b885181101561033f576001600160a01b036102e9828b61138f565b511690811561032e577feb1b9b92e50b7f88f9ff25d56765095ac6e91540eee214906f4036a908ffbdef8c83610320600195611503565b50604051908152a1016102ce565b6342bcdf7f60e11b60005260046000fd5b5081518a985089906001600160a01b0316158015610e44575b8015610e35575b610e245791516001600160a01b031660a05290516001600160601b03166080525163ffffffff1660c05261039286611295565b9360008552600036813760005b855181101561040e576001906103c76001600160a01b036103c0838a61138f565b5116611410565b6103d2575b0161039f565b818060a01b036103e2828961138f565b51167f1795838dc8ab2ffc5f431a1729a6afa0b587f982f7b2be0b9d7187a1ef547f91600080a26103cc565b508694508560005b84518110156104855760019061043e6001600160a01b03610437838961138f565b5116611542565b610449575b01610416565b818060a01b03610459828861138f565b51167fdf1b1bd32a69711488d71554706bb130b1fc63a5fa1a2cd85e8440f84065ba23600080a2610443565b508593508460005b835181101561054757806104a36001928661138f565b517fe6a7a17d710bf0b2cd05e5397dc6f97a5da4ee79e31e234bf5f965ee2bd9a5bf606089858060a01b038451169301518360005260078b5260406000209060ff878060a01b038251169283898060a01b03198254161781558d8301908151604082549501948460a81b8651151560a81b16918560a01b9060a01b169061ffff60a01b19161717905560405193845251168c8301525115156040820152a20161048d565b5091509160005b8251811015610abc57610561818461138f565b51856001600160401b03610575848761138f565b5151169101519080158015610aa9575b8015610a8b575b610a7757600081815260098852604090205460019392919060081b6001600160e01b03191661092f57807f71e9302ab4e912a9678ae7f5a8542856706806f2817e1bf2a20b171e265cb4ad604051806106f5868291909161024063ffffffff8161026084019580511515855261ffff602082015116602086015282604082015116604086015282606082015116606086015282608082015116608086015260ff60a08201511660a086015260ff60c08201511660c086015261ffff60e08201511660e0860152826101008201511661010086015261ffff6101208201511661012086015261ffff610140820151166101408601528260e01b61016082015116610160860152610180810151151561018086015261ffff6101a0820151166101a0860152826101c0820151166101c0860152826101e0820151166101e086015260018060401b03610200820151166102008601528261022082015116610220860152015116910152565b0390a25b60005260098752826040600020825115158382549162ffff008c83015160081b169066ffffffff000000604084015160181b166affffffff00000000000000606085015160381b16926effffffff0000000000000000000000608086015160581b169260ff60781b60a087015160781b169460ff60801b60c088015160801b169161ffff60881b60e089015160881b169063ffffffff60981b6101008a015160981b169361ffff60b81b6101208b015160b81b169661ffff60c81b6101408c015160c81b169963ffffffff60d81b6101608d015160081c169b61018060ff60f81b910151151560f81b169c8f8060f81b039a63ffffffff60d81b199961ffff60c81b199861ffff60b81b199763ffffffff60981b199661ffff60881b199560ff60801b199460ff60781b19936effffffff0000000000000000000000199260ff6affffffff000000000000001992169066ffffffffffffff19161716171617161716171617161716171617161716179063ffffffff60d81b1617178155019061ffff6101a0820151169082549165ffffffff00006101c083015160101b169269ffffffff0000000000006101e084015160301b166a01000000000000000000008860901b0361020085015160501b169263ffffffff60901b61022086015160901b169461024063ffffffff60b01b91015160b01b169563ffffffff60b01b199363ffffffff60901b19926a01000000000000000000008c60901b0319918c8060501b03191617161716171617171790550161054e565b807f2431cc0363f2f66b21782c7e3d54dd9085927981a21bd0cc6be45a51b19689e360405180610a6f868291909161024063ffffffff8161026084019580511515855261ffff602082015116602086015282604082015116604086015282606082015116606086015282608082015116608086015260ff60a08201511660a086015260ff60c08201511660c086015261ffff60e08201511660e0860152826101008201511661010086015261ffff6101208201511661012086015261ffff610140820151166101408601528260e01b61016082015116610160860152610180810151151561018086015261ffff6101a0820151166101a0860152826101c0820151166101c0860152826101e0820151166101e086015260018060401b03610200820151166102008601528261022082015116610220860152015116910152565b0390a26106f9565b63c35aa79d60e01b60005260045260246000fd5b5063ffffffff6101e08301511663ffffffff6060840151161061058c565b5063ffffffff6101e08301511615610585565b84828560005b8151811015610b42576001906001600160a01b03610ae0828561138f565b5151167fbb77da6f7210cdd16904228a9360133d1d7dfff99b1bc75f128da5b53e28f97d86848060401b0381610b16868961138f565b510151168360005260088252604060002081878060401b0319825416179055604051908152a201610ac2565b83600184610b4f83611295565b9060008252600092610e1f575b909282935b8251851015610d5e57610b74858461138f565b5180516001600160401b0316939083019190855b83518051821015610d4d57610b9e82879261138f565b51015184516001600160a01b0390610bb790849061138f565b5151169063ffffffff815116908781019163ffffffff8351169081811015610d385750506080810163ffffffff815116898110610d21575090899291838c52600a8a5260408c20600160a01b6001900386168d528a5260408c2092825163ffffffff169380549180518d1b67ffffffff0000000016916040860192835160401b69ffff000000000000000016966060810195865160501b6dffffffff00000000000000000000169063ffffffff60701b895160701b169260a001998b60ff60901b8c51151560901b169560ff60901b199363ffffffff60701b19926dffffffff000000000000000000001991600160501b60019003191617161716171617171790556040519586525163ffffffff168c8601525161ffff1660408501525163ffffffff1660608401525163ffffffff16608083015251151560a082015260c07f94967ae9ea7729ad4f54021c1981765d2b1d954f7c92fbec340aa0a54f46b8b591a3600101610b88565b6312766e0160e11b8c52600485905260245260448bfd5b6305a7b3d160e11b8c5260045260245260448afd5b505060019096019593509050610b61565b9150825b8251811015610de0576001906001600160401b03610d80828661138f565b515116828060a01b0384610d94848861138f565b5101511690808752600a855260408720848060a01b038316885285528660408120557f4de5b1bcbca6018c11303a2c3f4a4b4f22a1c741d8c4ba430d246ac06c5ddf8b8780a301610d62565b604051615a519081611610823960805181818161044201526129ff015260a05181818161047801526129b0015260c05181818161049f01526136d60152f35b610b5c565b63d794ef9560e01b60005260046000fd5b5063ffffffff8251161561035f565b5080516001600160601b031615610358565b639b15e16f60e01b60005260046000fd5b838203610280811261102957610260610e7e611276565b91610e888761136c565b8352601f190112611029576040519161026083016001600160401b0381118482101761102e57604052610ebd6020870161135f565b8352610ecb60408701611380565b6020840152610edc606087016112ce565b6040840152610eed608087016112ce565b6060840152610efe60a087016112ce565b6080840152610f0f60c08701611351565b60a0840152610f2060e08701611351565b60c0840152610f326101008701611380565b60e0840152610f4461012087016112ce565b610100840152610f576101408701611380565b610120840152610f6a6101608701611380565b610140840152610180860151916001600160e01b0319831683036110295783602093610160610280960152610fa26101a0890161135f565b610180820152610fb56101c08901611380565b6101a0820152610fc86101e089016112ce565b6101c0820152610fdb61020089016112ce565b6101e0820152610fee610220890161136c565b61020082015261100161024089016112ce565b61022082015261101461026089016112ce565b61024082015283820152815201930192610211565b600080fd5b634e487b7160e01b600052604160045260246000fd5b60408387031261102957602060409161105b611276565b611064866112ba565b815261107183870161136c565b838201528152019201916101b9565b82516001600160401b0381116110295782016040818803601f190112611029576110a8611276565b906110b56020820161136c565b825260408101516001600160401b03811161102957602091010187601f820112156110295780516110e86100e8826112df565b91602060e08185858152019302820101908a821161102957602001915b8183106111245750505091816020938480940152815201920191610162565b828b0360e081126110295760c0611139611276565b91611143866112ba565b8352601f190112611029576040519160c08301916001600160401b0383118484101761102e5760e09360209360405261117d8488016112ce565b815261118b604088016112ce565b8482015261119b60608801611380565b60408201526111ac608088016112ce565b60608201526111bd60a088016112ce565b60808201526111ce60c0880161135f565b60a082015283820152815201920191611105565b828403608081126110295760606111f7611276565b91611201866112ba565b8352601f1901126110295760809160209161121a611257565b6112258488016112ba565b815261123360408801611351565b848201526112436060880161135f565b60408201528382015281520192019161010a565b60405190606082016001600160401b0381118382101761102e57604052565b60408051919082016001600160401b0381118382101761102e57604052565b6040519190601f01601f191682016001600160401b0381118382101761102e57604052565b51906001600160a01b038216820361102957565b519063ffffffff8216820361102957565b6001600160401b03811161102e5760051b60200190565b9080601f830112156110295781516113106100e8826112df565b9260208085848152019260051b82010192831161102957602001905b8282106113395750505090565b60208091611346846112ba565b81520191019061132c565b519060ff8216820361102957565b5190811515820361102957565b51906001600160401b038216820361102957565b519061ffff8216820361102957565b80518210156113a35760209160051b010190565b634e487b7160e01b600052603260045260246000fd5b80548210156113a35760005260206000200190600090565b805480156113fa5760001901906113e882826113b9565b8154906000199060031b1b1916905555565b634e487b7160e01b600052603160045260246000fd5b6000818152600c602052604090205480156114d15760001981018181116114bb57600b546000198101919082116114bb5781810361146a575b505050611456600b6113d1565b600052600c60205260006040812055600190565b6114a361147b61148c93600b6113b9565b90549060031b1c928392600b6113b9565b819391549060031b91821b91600019901b19161790565b9055600052600c602052604060002055388080611449565b634e487b7160e01b600052601160045260246000fd5b5050600090565b8054906801000000000000000082101561102e578161148c9160016114ff940181556113b9565b9055565b8060005260036020526040600020541560001461153c576115258160026114d8565b600254906000526003602052604060002055600190565b50600090565b80600052600c6020526040600020541560001461153c5761156481600b6114d8565b600b5490600052600c602052604060002055600190565b60008181526003602052604090205480156114d15760001981018181116114bb576002546000198101919082116114bb578082036115d5575b5050506115c160026113d1565b600052600360205260006040812055600190565b6115f76115e661148c9360026113b9565b90549060031b1c92839260026113b9565b905560005260036020526040600020553880806115b456fe6080604052600436101561001257600080fd5b60003560e01c806241e5be1461020657806301ffc9a714610201578063061877e3146101fc57806306285c69146101f7578063181f5a77146101f25780632451a627146101ed578063325c868e146101e85780633937306f146101e357806341ed29e7146101de578063430d138c146101d957806345ac924d146101d45780634ab35b0b146101cf578063514e8cff146101ca5780636def4ce7146101c5578063770e2dc4146101c057806379ba5097146101bb5780637afac322146101b6578063805f2132146101b157806382b49eb0146101ac57806387b8d879146101a75780638da5cb5b146101a257806391a2749a1461019d578063a69c64c014610198578063bf78e03f14610193578063cdc73d511461018e578063d02641a014610189578063d63d3af214610184578063d8694ccd1461017f578063f2fde38b1461017a578063fbe3f778146101755763ffdb4b371461017057600080fd5b612556565b612459565b61239d565b611f78565b611f5c565b611f13565b611e9c565b611df6565b611d3d565b611ca9565b611c82565b611a66565b6118e9565b61164e565b611515565b6113fd565b6111de565b61105f565b610e88565b610e50565b610d87565b610c5a565b6109ff565b610743565b610727565b6106a4565b6105fe565b610406565b6103be565b61029a565b61022e565b6001600160a01b0381160361021c57565b600080fd5b359061022c8261020b565b565b3461021c57606060031936011261021c5760206102656004356102508161020b565b602435604435916102608361020b565b6126d8565b604051908152f35b35907fffffffff000000000000000000000000000000000000000000000000000000008216820361021c57565b3461021c57602060031936011261021c576004357fffffffff000000000000000000000000000000000000000000000000000000008116810361021c577fffffffff00000000000000000000000000000000000000000000000000000000602091167f805f2132000000000000000000000000000000000000000000000000000000008114908115610394575b811561036a575b8115610340575b506040519015158152f35b7f01ffc9a70000000000000000000000000000000000000000000000000000000091501438610335565b7f181f5a77000000000000000000000000000000000000000000000000000000008114915061032e565b7f9b645f410000000000000000000000000000000000000000000000000000000081149150610327565b3461021c57602060031936011261021c576001600160a01b036004356103e38161020b565b166000526008602052602067ffffffffffffffff60406000205416604051908152f35b3461021c57600060031936011261021c5761041f61270c565b50606060405161042e81610506565b63ffffffff6bffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016918281526001600160a01b0360406020830192827f00000000000000000000000000000000000000000000000000000000000000001684520191837f00000000000000000000000000000000000000000000000000000000000000001683526040519485525116602084015251166040820152f35b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff82111761052257604052565b6104d7565b60a0810190811067ffffffffffffffff82111761052257604052565b6040810190811067ffffffffffffffff82111761052257604052565b60c0810190811067ffffffffffffffff82111761052257604052565b90601f601f19910116810190811067ffffffffffffffff82111761052257604052565b6040519061022c60408361057b565b6040519061022c6102608361057b565b919082519283825260005b8481106105e9575050601f19601f8460006020809697860101520116010190565b806020809284010151828286010152016105c8565b3461021c57600060031936011261021c5761065d6040805190610621818361057b565b601382527f46656551756f74657220312e362e302d646576000000000000000000000000006020830152519182916020835260208301906105bd565b0390f35b602060408183019282815284518094520192019060005b8181106106855750505090565b82516001600160a01b0316845260209384019390920191600101610678565b3461021c57600060031936011261021c5760405180602060025491828152019060026000527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace9060005b8181106107115761065d856107058187038261057b565b60405191829182610661565b82548452602090930192600192830192016106ee565b3461021c57600060031936011261021c57602060405160248152f35b3461021c57602060031936011261021c5760043567ffffffffffffffff811161021c5780600401906040600319823603011261021c57610781613a0f565b61078b828061272b565b4263ffffffff1692915060005b8181106108fc575050602401906107af828461272b565b92905060005b8381106107be57005b806107dd6107d86001936107d2868a61272b565b906127ae565b61280e565b7fdd84a3fa9ef9409f550d54d6affec7e9c480c878c6ab27b78912a03e1b371c6e67ffffffffffffffff6108d86108ca60208501946108bc61082687516001600160e01b031690565b61084061083161059e565b6001600160e01b039092168252565b63ffffffff8c16602082015261087b610861845167ffffffffffffffff1690565b67ffffffffffffffff166000526005602052604060002090565b815160209092015160e01b7fffffffff00000000000000000000000000000000000000000000000000000000166001600160e01b0392909216919091179055565b5167ffffffffffffffff1690565b93516001600160e01b031690565b604080516001600160e01b039290921682524260208301529190931692a2016107b5565b806109156109106001936107d2898061272b565b6127d7565b7f52f50aa6d1a95a4595361ecf953d095f125d442e4673716dede699e049de148a6001600160a01b036109ae6108ca60208501946109a161095d87516001600160e01b031690565b61096861083161059e565b63ffffffff8d16602082015261087b61098884516001600160a01b031690565b6001600160a01b03166000526006602052604060002090565b516001600160a01b031690565b604080516001600160e01b039290921682524260208301529190931692a201610798565b67ffffffffffffffff81116105225760051b60200190565b8015150361021c57565b359061022c826109ea565b3461021c57602060031936011261021c5760043567ffffffffffffffff811161021c573660238201121561021c57806004013590610a3c826109d2565b90610a4a604051928361057b565b828252602460a06020840194028201019036821161021c57602401925b818410610a7957610a7783612833565b005b60a08436031261021c5760405190610a9082610527565b8435610a9b8161020b565b825260208501357fffffffffffffffffffff000000000000000000000000000000000000000000008116810361021c5760208301526040850135907fffff0000000000000000000000000000000000000000000000000000000000008216820361021c5782602092604060a0950152610b1660608801610221565b6060820152610b27608088016109f4565b6080820152815201930192610a67565b6004359067ffffffffffffffff8216820361021c57565b6024359067ffffffffffffffff8216820361021c57565b359067ffffffffffffffff8216820361021c57565b9181601f8401121561021c5782359167ffffffffffffffff831161021c576020838186019501011161021c57565b9181601f8401121561021c5782359167ffffffffffffffff831161021c576020808501948460051b01011161021c57565b929091610bfa928452151560208401526080604084015260808301906105bd565b906060818303910152815180825260208201916020808360051b8301019401926000915b838310610c2d57505050505090565b9091929394602080610c4b83601f19866001960301875289516105bd565b97019301930191939290610c1e565b3461021c5760c060031936011261021c57610c73610b37565b60243590610c808261020b565b60443560643567ffffffffffffffff811161021c57610ca3903690600401610b7a565b60849391933567ffffffffffffffff811161021c57610cc6903690600401610ba8565b9160a4359567ffffffffffffffff871161021c573660238801121561021c5786600401359567ffffffffffffffff871161021c573660248860061b8a01011161021c5761065d986024610d1a9901966129a5565b9060409492945194859485610bd9565b602060408183019282815284518094520192019060005b818110610d4e5750505090565b9091926020604082610d7c600194885163ffffffff602080926001600160e01b038151168552015116910152565b019401929101610d41565b3461021c57602060031936011261021c5760043567ffffffffffffffff811161021c57610db8903690600401610ba8565b610dc1816109d2565b91610dcf604051938461057b565b818352601f19610dde836109d2565b0160005b818110610e3957505060005b82811015610e2b57600190610e0f610e0a8260051b8501612cbc565b613682565b610e198287612991565b52610e248186612991565b5001610dee565b6040518061065d8682610d2a565b602090610e44612ca3565b82828801015201610de2565b3461021c57602060031936011261021c576020610e77600435610e728161020b565b613997565b6001600160e01b0360405191168152f35b3461021c57602060031936011261021c5767ffffffffffffffff610eaa610b37565b610eb2612ca3565b50166000526005602052604060002060405190610ece82610543565b546001600160e01b038116825260e01c6020820152604051809161065d82604081019263ffffffff602080926001600160e01b038151168552015116910152565b61022c9092919261024080610260830195610f2c84825115159052565b60208181015161ffff169085015260408181015163ffffffff169085015260608181015163ffffffff169085015260808181015163ffffffff169085015260a08181015160ff169085015260c08181015160ff169085015260e08181015161ffff16908501526101008181015163ffffffff16908501526101208181015161ffff16908501526101408181015161ffff1690850152610160818101517fffffffff000000000000000000000000000000000000000000000000000000001690850152610180818101511515908501526101a08181015161ffff16908501526101c08181015163ffffffff16908501526101e08181015163ffffffff16908501526102008181015167ffffffffffffffff16908501526102208181015163ffffffff1690850152015163ffffffff16910152565b3461021c57602060031936011261021c5761065d61112261111d611081610b37565b600061024061108e6105ad565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152826101408201528261016082015282610180820152826101a0820152826101c0820152826101e08201528261020082015282610220820152015267ffffffffffffffff166000526009602052604060002090565b612ceb565b60405191829182610f0f565b359063ffffffff8216820361021c57565b359061ffff8216820361021c57565b81601f8201121561021c57803590611165826109d2565b92611173604051948561057b565b82845260208085019360061b8301019181831161021c57602001925b82841061119d575050505090565b60408483031261021c57602060409182516111b781610543565b6111c087610b65565b8152828701356111cf8161020b565b8382015281520193019261118f565b3461021c57604060031936011261021c5760043567ffffffffffffffff811161021c573660238201121561021c57806004013561121a816109d2565b91611228604051938461057b565b8183526024602084019260051b8201019036821161021c5760248101925b828410611277576024358567ffffffffffffffff821161021c57611271610a7792369060040161114e565b90612e59565b833567ffffffffffffffff811161021c57820160407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc823603011261021c57604051906112c382610543565b6112cf60248201610b65565b8252604481013567ffffffffffffffff811161021c57602491010136601f8201121561021c578035611300816109d2565b9161130e604051938461057b565b818352602060e081850193028201019036821161021c57602001915b8183106113495750505091816020938480940152815201930192611246565b82360360e0811261021c5760c0601f196040519261136684610543565b86356113718161020b565b8452011261021c5760e09160209160405161138b8161055f565b61139684880161112e565b81526113a46040880161112e565b848201526113b46060880161113f565b60408201526113c56080880161112e565b60608201526113d660a0880161112e565b608082015260c08701356113e9816109ea565b60a08201528382015281520192019161132a565b3461021c57600060031936011261021c576000546001600160a01b0381163303611484577fffffffffffffffffffffffff0000000000000000000000000000000000000000600154913382841617600155166000556001600160a01b033391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b7f02b543c60000000000000000000000000000000000000000000000000000000060005260046000fd5b9080601f8301121561021c5781356114c5816109d2565b926114d3604051948561057b565b81845260208085019260051b82010192831161021c57602001905b8282106114fb5750505090565b60208091833561150a8161020b565b8152019101906114ee565b3461021c57604060031936011261021c5760043567ffffffffffffffff811161021c576115469036906004016114ae565b60243567ffffffffffffffff811161021c576115669036906004016114ae565b9061156f613a53565b60005b81518110156115de578061159361158e6109a160019486612991565b615656565b61159e575b01611572565b6001600160a01b036115b36109a18386612991565b167f1795838dc8ab2ffc5f431a1729a6afa0b587f982f7b2be0b9d7187a1ef547f91600080a2611598565b8260005b8151811015610a7757806116036115fe6109a160019486612991565b61566a565b61160e575b016115e2565b6001600160a01b036116236109a18386612991565b167fdf1b1bd32a69711488d71554706bb130b1fc63a5fa1a2cd85e8440f84065ba23600080a2611608565b3461021c57604060031936011261021c5760043567ffffffffffffffff811161021c5761167f903690600401610b7a565b6024359167ffffffffffffffff831161021c576116d86116d06116b66116ac6116e0963690600401610b7a565b9490953691613108565b90604082015190605e604a84015160601c93015191929190565b919033613eb7565b81019061314f565b60005b8151811015610a775761172b61172661170d6116ff8486612991565b51516001600160a01b031690565b6001600160a01b03166000526007602052604060002090565b61320e565b61173f61173b6040830151151590565b1590565b6118a0579061178a6117576020600194015160ff1690565b61178461177860206117698689612991565b5101516001600160e01b031690565b6001600160e01b031690565b90613f89565b6117a560406117998487612991565b51015163ffffffff1690565b63ffffffff6117d06117c76117c06109886116ff888b612991565b5460e01c90565b63ffffffff1690565b91161061189a5761181e6117e960406117998588612991565b61180e6117f461059e565b6001600160e01b03851681529163ffffffff166020830152565b61087b6109886116ff8689612991565b7f52f50aa6d1a95a4595361ecf953d095f125d442e4673716dede699e049de148a6001600160a01b036118546116ff8588612991565b6118906118666040611799888b612991565b60405193849316958390929163ffffffff6020916001600160e01b03604085019616845216910152565b0390a25b016116e3565b50611894565b6118e56118b06116ff8486612991565b7f06439c6b000000000000000000000000000000000000000000000000000000006000526001600160a01b0316600452602490565b6000fd5b3461021c57604060031936011261021c5761065d611971611908610b37565b67ffffffffffffffff6024359161191e8361020b565b600060a060405161192e8161055f565b828152826020820152826040820152826060820152826080820152015216600052600a6020526040600020906001600160a01b0316600052602052604060002090565b6119ed6119e4604051926119848461055f565b5463ffffffff8116845263ffffffff8160201c16602085015261ffff8160401c1660408501526119cb6119be8263ffffffff9060501c1690565b63ffffffff166060860152565b63ffffffff607082901c16608085015260901c60ff1690565b151560a0830152565b6040519182918291909160a08060c083019463ffffffff815116845263ffffffff602082015116602085015261ffff604082015116604085015263ffffffff606082015116606085015263ffffffff608082015116608085015201511515910152565b60ff81160361021c57565b359061022c82611a50565b3461021c57602060031936011261021c5760043567ffffffffffffffff811161021c573660238201121561021c57806004013590611aa3826109d2565b90611ab1604051928361057b565b82825260246102806020840194028201019036821161021c57602401925b818410611adf57610a778361329e565b833603610280811261021c57610260601f1960405192611afe84610543565b611b0788610b65565b8452011261021c5761028091602091611b1e6105ad565b611b298489016109f4565b8152611b376040890161113f565b84820152611b476060890161112e565b6040820152611b586080890161112e565b6060820152611b6960a0890161112e565b6080820152611b7a60c08901611a5b565b60a0820152611b8b60e08901611a5b565b60c0820152611b9d610100890161113f565b60e0820152611baf610120890161112e565b610100820152611bc2610140890161113f565b610120820152611bd5610160890161113f565b610140820152611be8610180890161026d565b610160820152611bfb6101a089016109f4565b610180820152611c0e6101c0890161113f565b6101a0820152611c216101e0890161112e565b6101c0820152611c34610200890161112e565b6101e0820152611c476102208901610b65565b610200820152611c5a610240890161112e565b610220820152611c6d610260890161112e565b61024082015283820152815201930192611acf565b3461021c57600060031936011261021c5760206001600160a01b0360015416604051908152f35b3461021c57602060031936011261021c5760043567ffffffffffffffff811161021c576040600319823603011261021c57604051611ce681610543565b816004013567ffffffffffffffff811161021c57611d0a90600436918501016114ae565b8152602482013567ffffffffffffffff811161021c57610a77926004611d3392369201016114ae565b6020820152613454565b3461021c57602060031936011261021c5760043567ffffffffffffffff811161021c573660238201121561021c57806004013590611d7a826109d2565b90611d88604051928361057b565b8282526024602083019360061b8201019036821161021c57602401925b818410611db557610a77836135a6565b60408436031261021c5760206040918251611dcf81610543565b8635611dda8161020b565b8152611de7838801610b65565b83820152815201930192611da5565b3461021c57602060031936011261021c576001600160a01b03600435611e1b8161020b565b611e2361270c565b5016600052600760205261065d604060002060ff60405191611e4483610506565b546001600160a01b0381168352818160a01c16602084015260a81c16151560408201526040519182918291909160408060608301946001600160a01b03815116845260ff602082015116602085015201511515910152565b3461021c57600060031936011261021c57604051806020600b54918281520190600b6000527f0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db99060005b818110611efd5761065d856107058187038261057b565b8254845260209093019260019283019201611ee6565b3461021c57602060031936011261021c576040611f35600435610e0a8161020b565b611f5a8251809263ffffffff602080926001600160e01b038151168552015116910152565bf35b3461021c57600060031936011261021c57602060405160128152f35b3461021c57604060031936011261021c57611f91610b37565b60243567ffffffffffffffff811161021c57806004019060a0600319823603011261021c57611fd761111d8467ffffffffffffffff166000526009602052604060002090565b606482019161200a61173b611feb85612cbc565b6001600160a01b03166000526001600b01602052604060002054151590565b61235c579081604485930195612020878561272b565b959050602461203c85612036608487018961379e565b906148cb565b93019061206b61204c838861379e565b9050858961206461205d8b8061379e565b3691613108565b9289614a74565b84612078610e7283612cbc565b98899461209661209061022085015163ffffffff1690565b82614b36565b9b6000808c156123225750506120fc61ffff856121219961210899989661213c9661212f966120f36120e36101c06120d76101a06121429f015161ffff1690565b97015163ffffffff1690565b916120ed8c612cbc565b9461272b565b96909516614c27565b98919897909894612cbc565b6001600160a01b03166000526008602052604060002090565b5467ffffffffffffffff1690565b67ffffffffffffffff1690565b9061268c565b9560009761ffff61215961014089015161ffff1690565b166122c7575b509461213c61212f61020061222d61065d9d6dffffffffffffffffffffffffffff6122256122459f9e9b6122206001600160e01b039f9b9c61223d9f6122209e63ffffffff6121b46122209f6121be9461379e565b92905016906137ef565b908b60a081016121e16121db6121d5835160ff1690565b60ff1690565b8561268c565b9360e08301916121f3835161ffff1690565b9061ffff82168311612255575b5050505060800151612220916117c79163ffffffff1661382d565b61382d565b6137ef565b91169061268c565b93015167ffffffffffffffff1690565b91169061269f565b6040519081529081906020820190565b6117c7949650612220959361ffff6122b66122a561221b9661229f61229861228f60809960ff6122896122bd9b5160ff1690565b166137fc565b965161ffff1690565b61ffff1690565b90613675565b61213c6121d560c08d015160ff1690565b91166137ef565b9593839550612200565b909594989750826122ed8b989495986dffffffffffffffffffffffffffff9060701c1690565b6dffffffffffffffffffffffffffff1691612308848961379e565b90506123149388614e49565b96979394389693929661215f565b969593509650505061213c61212f6121216121086123566123516117c761024061214299015163ffffffff1690565b612645565b94612cbc565b6118e561236884612cbc565b7f2502348c000000000000000000000000000000000000000000000000000000006000526001600160a01b0316600452602490565b3461021c57602060031936011261021c576001600160a01b036004356123c28161020b565b6123ca613a53565b1633811461242f57807fffffffffffffffffffffffff000000000000000000000000000000000000000060005416176000556001600160a01b03600154167fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278600080a3005b7fdad89dca0000000000000000000000000000000000000000000000000000000060005260046000fd5b3461021c57602060031936011261021c5760043567ffffffffffffffff811161021c573660238201121561021c57806004013590612496826109d2565b906124a4604051928361057b565b8282526024602083019360071b8201019036821161021c57602401925b8184106124d157610a7783613847565b8336036080811261021c576060601f19604051926124ee84610543565b87356124f98161020b565b8452011261021c5760809160209160405161251381610506565b838801356125208161020b565b8152604088013561253081611a50565b848201526060880135612542816109ea565b6040820152838201528152019301926124c1565b3461021c57604060031936011261021c576004356125738161020b565b61257b610b4e565b9067ffffffffffffffff82169182600052600960205260ff60406000205416156125e8576125ab6125cc92613997565b92600052600960205263ffffffff60016040600020015460901c1690614b36565b604080516001600160e01b039384168152919092166020820152f35b827f99ac52f20000000000000000000000000000000000000000000000000000000060005260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b90662386f26fc10000820291808304662386f26fc10000149015171561266757565b612616565b90655af3107a4000820291808304655af3107a4000149015171561266757565b8181029291811591840414171561266757565b81156126a9570490565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6127026126fc61270994936001600160e01b036126f58195613997565b169061268c565b92613997565b169061269f565b90565b6040519061271982610506565b60006040838281528260208201520152565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561021c570180359067ffffffffffffffff821161021c57602001918160061b3603831361021c57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b91908110156127be5760061b0190565b61277f565b35906001600160e01b038216820361021c57565b60408136031261021c576128066020604051926127f384610543565b80356127fe8161020b565b8452016127c3565b602082015290565b60408136031261021c5761280660206040519261282a84610543565b6127fe81610b65565b9061283c613a53565b60005b825181101561298c578061285560019285612991565b517f32a4ba3fa3351b11ad555d4c8ec70a744e8705607077a946807030d64b6ab1a360a06001600160a01b038351169260608101936001600160a01b0380865116957fffff0000000000000000000000000000000000000000000000000000000000006128f360208601947fffffffffffffffffffff00000000000000000000000000000000000000000000865116604088019a848c511692614eee565b977fffffffffffffffffffff00000000000000000000000000000000000000000000608087019561295f875115158c600052600460205260406000209060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b8560405198511688525116602087015251166040850152511660608301525115156080820152a20161283f565b509050565b80518210156127be5760209160051b010190565b9895979692949391907f0000000000000000000000000000000000000000000000000000000000000000906001600160a01b0382166001600160a01b03821614600014612c93575050935b6bffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016808611612c62575090612a35918715158a613a91565b979096612a82612a598367ffffffffffffffff166000526009602052604060002090565b5460081b7fffffffff000000000000000000000000000000000000000000000000000000001690565b90612a8c81613cda565b9760005b828110612aa4575050505050505093929190565b612ab7612ab28284896127ae565b612cbc565b8388612ad1612ac7858484613d23565b604081019061379e565b905060208111612be7575b508392612b0b612b0561205d612afb600198612b4697612b4197613d23565b602081019061379e565b8961541f565b612b298967ffffffffffffffff16600052600a602052604060002090565b906001600160a01b0316600052602052604060002090565b613244565b60a081015115612bab57612b8f612b676060612b8193015163ffffffff1690565b6040805163ffffffff909216602083015290928391820190565b03601f19810183528261057b565b612b99828d612991565b52612ba4818c612991565b5001612a90565b50612b81612b8f612be284612bd48a67ffffffffffffffff166000526009602052604060002090565b015460101c63ffffffff1690565b612b67565b915050612c1f6117c7612c1284612b298b67ffffffffffffffff16600052600a602052604060002090565b5460701c63ffffffff1690565b10612c2c57838838612adc565b7f36f536ca000000000000000000000000000000000000000000000000000000006000526001600160a01b031660045260246000fd5b857f6a92a4830000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b91612c9d926126d8565b936129f0565b60405190612cb082610543565b60006020838281520152565b356127098161020b565b90604051612cd381610543565b91546001600160e01b038116835260e01c6020830152565b9061022c612e4b6001612cfc6105ad565b94612dea612de08254612d18612d128260ff1690565b15158a52565b61ffff600882901c1660208a015263ffffffff601882901c1660408a015263ffffffff603882901c1660608a015263ffffffff605882901c1660808a015260ff607882901c1660a08a015260ff608082901c1660c08a015261ffff608882901c1660e08a015263ffffffff609882901c166101008a015261ffff60b882901c166101208a015261ffff60c882901c166101408a01527fffffffff00000000000000000000000000000000000000000000000000000000600882901b166101608a015260f81c90565b1515610180880152565b015461ffff81166101a086015263ffffffff601082901c166101c086015263ffffffff603082901c166101e086015267ffffffffffffffff605082901c1661020086015263ffffffff609082901c1661022086015260b01c63ffffffff1690565b63ffffffff16610240840152565b90612e62613a53565b6000915b805183101561305457612e798382612991565b5190612e8d825167ffffffffffffffff1690565b946020600093019367ffffffffffffffff8716935b8551805182101561303f57612eb982602092612991565b510151612eca6116ff838951612991565b8151602083015163ffffffff908116911681811015613006575050608082015163ffffffff1660208110612fc5575090867f94967ae9ea7729ad4f54021c1981765d2b1d954f7c92fbec340aa0a54f46b8b56001600160a01b0384612f54858f60019998612b29612f4f9267ffffffffffffffff16600052600a602052604060002090565b613d63565b612fbc60405192839216958291909160a08060c083019463ffffffff815116845263ffffffff602082015116602085015261ffff604082015116604085015263ffffffff606082015116606085015263ffffffff608082015116608085015201511515910152565b0390a301612ea2565b7f24ecdc02000000000000000000000000000000000000000000000000000000006000526001600160a01b0390911660045263ffffffff1660245260446000fd5b7f0b4f67a20000000000000000000000000000000000000000000000000000000060005263ffffffff9081166004521660245260446000fd5b50509550925092600191500191929092612e66565b50905060005b8151811015613104578061308261307360019385612991565b515167ffffffffffffffff1690565b67ffffffffffffffff6001600160a01b036130b160206130a28689612991565b5101516001600160a01b031690565b60006130d582612b298767ffffffffffffffff16600052600a602052604060002090565b551691167f4de5b1bcbca6018c11303a2c3f4a4b4f22a1c741d8c4ba430d246ac06c5ddf8b600080a30161305a565b5050565b92919267ffffffffffffffff82116105225760405191613132601f8201601f19166020018461057b565b82948184528183011161021c578281602093846000960137010152565b60208183031261021c5780359067ffffffffffffffff821161021c570181601f8201121561021c57803590613183826109d2565b92613191604051948561057b565b8284526020606081860194028301019181831161021c57602001925b8284106131bb575050505090565b60608483031261021c5760206060916040516131d681610506565b86356131e18161020b565b81526131ee8388016127c3565b838201526131fe6040880161112e565b60408201528152019301926131ad565b9060405161321b81610506565b604060ff8294546001600160a01b0381168452818160a01c16602085015260a81c161515910152565b9061022c6040516132548161055f565b925463ffffffff8082168552602082811c821690860152604082811c61ffff1690860152605082901c81166060860152607082901c16608085015260901c60ff16151560a0840152565b906132a7613a53565b60005b825181101561298c576132bd8184612991565b5160206132cd6130738487612991565b9101519067ffffffffffffffff811680158015613435575b8015613407575b6133cf57916133958260019594613345613320612a5961339a9767ffffffffffffffff166000526009602052604060002090565b7fffffffff000000000000000000000000000000000000000000000000000000001690565b6133a0577f71e9302ab4e912a9678ae7f5a8542856706806f2817e1bf2a20b171e265cb4ad604051806133788782610f0f565b0390a267ffffffffffffffff166000526009602052604060002090565b614096565b016132aa565b7f2431cc0363f2f66b21782c7e3d54dd9085927981a21bd0cc6be45a51b19689e3604051806133788782610f0f565b7fc35aa79d0000000000000000000000000000000000000000000000000000000060005267ffffffffffffffff821660045260246000fd5b506101e083015163ffffffff1663ffffffff61342d6117c7606087015163ffffffff1690565b9116116132ec565b5063ffffffff61344d6101e085015163ffffffff1690565b16156132e5565b61345c613a53565b60208101519160005b83518110156134e9578061347e6109a160019387612991565b6134a061349b6001600160a01b0383165b6001600160a01b031690565b6159b9565b6134ac575b5001613465565b6040516001600160a01b039190911681527fc3803387881faad271c47728894e3e36fac830ffc8602ca6fc07733cbda7758090602090a1386134a5565b5091505160005b8151811015613104576135066109a18284612991565b906001600160a01b0382161561357c577feb1b9b92e50b7f88f9ff25d56765095ac6e91540eee214906f4036a908ffbdef6135738361355861355361348f6001976001600160a01b031690565b615940565b506040516001600160a01b0390911681529081906020820190565b0390a1016134f0565b7f8579befe0000000000000000000000000000000000000000000000000000000060005260046000fd5b6135ae613a53565b60005b815181101561310457806001600160a01b036135cf60019385612991565b5151167fbb77da6f7210cdd16904228a9360133d1d7dfff99b1bc75f128da5b53e28f97d61366c67ffffffffffffffff602061360b8689612991565b51015116836000526008602052604060002067ffffffffffffffff82167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000008254161790556040519182918291909167ffffffffffffffff6020820193169052565b0390a2016135b1565b9190820391821161266757565b61368a612ca3565b506136b06136ab826001600160a01b03166000526006602052604060002090565b612cc6565b60208101916136cf6136c96117c7855163ffffffff1690565b42613675565b63ffffffff7f000000000000000000000000000000000000000000000000000000000000000016116137775761172661371b916001600160a01b03166000526007602052604060002090565b61372b61173b6040830151151590565b801561377d575b6137775761373f90614764565b9163ffffffff6137676117c761375c602087015163ffffffff1690565b935163ffffffff1690565b911610613772575090565b905090565b50905090565b506001600160a01b0361379782516001600160a01b031690565b1615613732565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18136030182121561021c570180359067ffffffffffffffff821161021c5760200191813603831361021c57565b9190820180921161266757565b9061ffff8091169116029061ffff821691820361266757565b63ffffffff60209116019063ffffffff821161266757565b9063ffffffff8091169116019063ffffffff821161266757565b90613850613a53565b60005b825181101561298c578061386960019285612991565b517fe6a7a17d710bf0b2cd05e5397dc6f97a5da4ee79e31e234bf5f965ee2bd9a5bf61398e60206001600160a01b0384511693015183600052600760205260406000206138ee6001600160a01b0383511682906001600160a01b03167fffffffffffffffffffffffff0000000000000000000000000000000000000000825416179055565b602082015181547fffffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffff74ff000000000000000000000000000000000000000075ff0000000000000000000000000000000000000000006040870151151560a81b169360a01b169116171790556040519182918291909160408060608301946001600160a01b03815116845260ff602082015116602085015201511515910152565b0390a201613853565b6139a081613682565b9063ffffffff6020830151161580156139fd575b6139c65750516001600160e01b031690565b6001600160a01b03907f06439c6b000000000000000000000000000000000000000000000000000000006000521660045260246000fd5b506001600160e01b03825116156139b4565b33600052600360205260406000205415613a2557565b7fd86ad9cf000000000000000000000000000000000000000000000000000000006000523360045260246000fd5b6001600160a01b03600154163303613a6757565b7f2b5c74de0000000000000000000000000000000000000000000000000000000060005260046000fd5b61111d613ab791959493929567ffffffffffffffff166000526009602052604060002090565b936101608501947f2812d52c000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000613b2a88517fffffffff000000000000000000000000000000000000000000000000000000001690565b1614613ca6577f1e10bdc4000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000613b9c88517fffffffff000000000000000000000000000000000000000000000000000000001690565b1614613c1d576118e5613bcf87517fffffffff000000000000000000000000000000000000000000000000000000001690565b7f2ee82075000000000000000000000000000000000000000000000000000000006000527fffffffff0000000000000000000000000000000000000000000000000000000016600452602490565b60609192939495508063ffffffff613c4c610180613c4486613c5596015163ffffffff1690565b930151151590565b911686866152ba565b015181613c9d575b50613c7357613c6d913691613108565b90600190565b7f5bed51920000000000000000000000000000000000000000000000000000000060005260046000fd5b90501538613c5d565b6101e00151939450613ccb93919291613cc5915063ffffffff166117c7565b9161503b565b906127096020613c448461515c565b90613ce4826109d2565b613cf1604051918261057b565b828152601f19613d0182946109d2565b019060005b828110613d1257505050565b806060602080938501015201613d06565b91908110156127be5760051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff618136030182121561021c570190565b815181546020808501516040808701517fffffffffffffffffffffffffffffffffffffffffffff0000000000000000000090941663ffffffff958616179190921b67ffffffff00000000161791901b69ffff000000000000000016178255606083015161022c93613e739260a092613e15911685547fffffffffffffffffffffffffffffffffffff00000000ffffffffffffffffffff1660509190911b6dffffffff0000000000000000000016178555565b613e6c613e29608083015163ffffffff1690565b85547fffffffffffffffffffffffffffff00000000ffffffffffffffffffffffffffff1660709190911b71ffffffff000000000000000000000000000016178555565b0151151590565b81547fffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffff1690151560901b72ff00000000000000000000000000000000000016179055565b91929092613ec782828686614eee565b600052600460205260ff6040600020541615613ee35750505050565b6040517f097e17ff0000000000000000000000000000000000000000000000000000000081526001600160a01b0393841660048201529390921660248401527fffffffffffffffffffff0000000000000000000000000000000000000000000090911660448301527fffff000000000000000000000000000000000000000000000000000000000000166064820152608490fd5b0390fd5b604d811161266757600a0a90565b60ff1660120160ff81116126675760ff16906024821115614024577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc820191821161266757613fda613fe092613f7b565b9061269f565b6001600160e01b038111613ffa576001600160e01b031690565b7f10cb51d10000000000000000000000000000000000000000000000000000000060005260046000fd5b9060240390602482116126675761213c61403d92613f7b565b613fe0565b9060ff80911691160160ff81116126675760ff16906024821115614024577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc820191821161266757613fda613fe092613f7b565b906146b0610240600161022c946140e16140b08651151590565b829060ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0083541691151516179055565b6141276140f3602087015161ffff1690565b82547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000ff1660089190911b62ffff0016178255565b61417361413b604087015163ffffffff1690565b82547fffffffffffffffffffffffffffffffffffffffffffffffffff00000000ffffff1660189190911b66ffffffff00000016178255565b6141c3614187606087015163ffffffff1690565b82547fffffffffffffffffffffffffffffffffffffffffff00000000ffffffffffffff1660389190911b6affffffff0000000000000016178255565b6142176141d7608087015163ffffffff1690565b82547fffffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffff1660589190911b6effffffff000000000000000000000016178255565b61426961422860a087015160ff1690565b82547fffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffff1660789190911b6fff00000000000000000000000000000016178255565b6142bc61427a60c087015160ff1690565b82547fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff1660809190911b70ff0000000000000000000000000000000016178255565b6143126142ce60e087015161ffff1690565b82547fffffffffffffffffffffffffff0000ffffffffffffffffffffffffffffffffff1660889190911b72ffff000000000000000000000000000000000016178255565b61436f61432761010087015163ffffffff1690565b82547fffffffffffffffffff00000000ffffffffffffffffffffffffffffffffffffff1660989190911b76ffffffff0000000000000000000000000000000000000016178255565b6143cc61438261012087015161ffff1690565b82547fffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffffffff1660b89190911b78ffff000000000000000000000000000000000000000000000016178255565b61442b6143df61014087015161ffff1690565b82547fffffffffff0000ffffffffffffffffffffffffffffffffffffffffffffffffff1660c89190911b7affff0000000000000000000000000000000000000000000000000016178255565b6144ac61445c6101608701517fffffffff000000000000000000000000000000000000000000000000000000001690565b82547fff00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff1660089190911c7effffffff00000000000000000000000000000000000000000000000000000016178255565b61450d6144bd610180870151151590565b82547effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690151560f81b7fff0000000000000000000000000000000000000000000000000000000000000016178255565b01926145516145226101a083015161ffff1690565b859061ffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000825416179055565b61459d6145666101c083015163ffffffff1690565b85547fffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ffff1660109190911b65ffffffff000016178555565b6145ed6145b26101e083015163ffffffff1690565b85547fffffffffffffffffffffffffffffffffffffffffffff00000000ffffffffffff1660309190911b69ffffffff00000000000016178555565b61464961460661020083015167ffffffffffffffff1690565b85547fffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffff1660509190911b71ffffffffffffffff0000000000000000000016178555565b6146a561465e61022083015163ffffffff1690565b85547fffffffffffffffffffff00000000ffffffffffffffffffffffffffffffffffff1660909190911b75ffffffff00000000000000000000000000000000000016178555565b015163ffffffff1690565b7fffffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffff79ffffffff0000000000000000000000000000000000000000000083549260b01b169116179055565b519069ffffffffffffffffffff8216820361021c57565b908160a091031261021c57614725816146fa565b916020820151916040810151916127096080606084015193016146fa565b6040513d6000823e3d90fd5b9081602091031261021c575161270981611a50565b61476c612ca3565b5061478461348f61348f83516001600160a01b031690565b90604051907ffeaf968c00000000000000000000000000000000000000000000000000000000825260a082600481865afa92831561488c57600092600094614891575b5060008312613ffa576020600491604051928380927f313ce5670000000000000000000000000000000000000000000000000000000082525afa92831561488c576127099363ffffffff9361482d93600092614856575b506020015160ff165b90614042565b9261484861483961059e565b6001600160e01b039095168552565b1663ffffffff166020830152565b61482791925061487d602091823d8411614885575b614875818361057b565b81019061474f565b92915061481e565b503d61486b565b614743565b9093506148b791925060a03d60a0116148c4575b6148af818361057b565b810190614711565b50939250509192386147c7565b503d6148a5565b929190926101608201937f2812d52c000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000061494187517fffffffff000000000000000000000000000000000000000000000000000000001690565b1614614a2b577f1e10bdc4000000000000000000000000000000000000000000000000000000007fffffffff000000000000000000000000000000000000000000000000000000006149b387517fffffffff000000000000000000000000000000000000000000000000000000001690565b16146149e6576118e5613bcf86517fffffffff000000000000000000000000000000000000000000000000000000001690565b61270993945091614a21916117c79363ffffffff614a19610180614a11606087015163ffffffff1690565b950151151590565b9316916152ba565b5163ffffffff1690565b614a70939450614a436101e084015163ffffffff1690565b9063ffffffff614a68610180614a60606088015163ffffffff1690565b960151151590565b94169261567e565b5190565b9493919063ffffffff604087015116808211614b0657505061ffff60208601511690818111614ad05750507fffffffff0000000000000000000000000000000000000000000000000000000061016061022c9495015116615568565b61ffff92507fd88dddd6000000000000000000000000000000000000000000000000000000006000526004521660245260446000fd5b7f869337890000000000000000000000000000000000000000000000000000000060005260045260245260446000fd5b67ffffffffffffffff8116600052600560205260406000209160405192614b5c84610543565b546001600160e01b038116845260e01c9182602085015263ffffffff82169283614b96575b5050505061270990516001600160e01b031690565b63ffffffff164290810393908411612667578311614bb45780614b81565b7ff08bcb3e0000000000000000000000000000000000000000000000000000000060005267ffffffffffffffff1660045263ffffffff1660245260445260646000fd5b60408136031261021c57602060405191614c1083610543565b8035614c1b8161020b565b83520135602082015290565b9694919695929390956000946000986000986000965b808810614c51575050505050505050929190565b9091929394959697999a614c6e614c698a848b6127ae565b614bf7565b9a614cbc612b418d614ca5614c978967ffffffffffffffff16600052600a602052604060002090565b91516001600160a01b031690565b6001600160a01b0316600052602052604060002090565b91614ccd61173b60a0850151151590565b614e165760009c6040840190614ce8612298835161ffff1690565b614d9e575b5050606083015163ffffffff16614d039161382d565b9c6080830151614d169063ffffffff1690565b614d1f9161382d565b9b8251614d2f9063ffffffff1690565b63ffffffff16614d3e90612645565b60019390808310614d9257506123516117c76020614d6193015163ffffffff1690565b808211614d815750614d72916137ef565b985b0196959493929190614c3d565b9050614d8c916137ef565b98614d74565b915050614d8c916137ef565b9061213c614e07939f614df5614dfe9460208f8e61229895506001600160a01b03614dd085516001600160a01b031690565b91166001600160a01b03821614614e0f57614deb9150613997565b915b0151906156e6565b925161ffff1690565b620186a0900490565b9b3880614ced565b5091614ded565b999b5060019150614e3d84614e37614e4393614e318b612645565b906137ef565b9b61382d565b9c613815565b9a614d74565b91939093806101e00193846101e011612667576101208102908082046101201490151715612667576101e091010180931161266757612298610140614edf612709966dffffffffffffffffffffffffffff612225614eca614eb7614ee99a63ffffffff61213c9a16906137ef565b61213c6122986101208c015161ffff1690565b614e316117c76101008b015163ffffffff1690565b93015161ffff1690565b61266c565b604080516001600160a01b039283166020820190815292909316908301527fffffffffffffffffffff0000000000000000000000000000000000000000000090921660608201527fffff000000000000000000000000000000000000000000000000000000000000909216608083015290614f6c8160a08101612b81565b51902090565b919091357fffffffff0000000000000000000000000000000000000000000000000000000081169260048110614fa6575050565b7fffffffff00000000000000000000000000000000000000000000000000000000929350829060040360031b1b161690565b909291928360041161021c57831161021c57600401916003190190565b9060041161021c5790600490565b9081602091031261021c575190565b9081604091031261021c5760206040519161502c83610543565b805183520151612806816109ea565b91615044612ca3565b50811561513a575061508561205d828061507f7fffffffff000000000000000000000000000000000000000000000000000000009587614f72565b95614fd8565b91167f181dcf100000000000000000000000000000000000000000000000000000000081036150c257508060208061270993518301019101615012565b7f97a657c90000000000000000000000000000000000000000000000000000000014615112577f5247fdce0000000000000000000000000000000000000000000000000000000060005260046000fd5b8060208061512593518301019101615003565b61512d61059e565b9081526000602082015290565b91505067ffffffffffffffff61514e61059e565b911681526000602082015290565b6020604051917f181dcf100000000000000000000000000000000000000000000000000000000082840152805160248401520151151560448201526044815261270960648261057b565b604051906151b382610527565b60606080836000815260006020820152600060408201526000838201520152565b60208183031261021c5780359067ffffffffffffffff821161021c57019060a08282031261021c576040519161520983610527565b6152128161112e565b835261522060208201610b65565b60208401526040810135615233816109ea565b60408401526060810135606084015260808101359067ffffffffffffffff821161021c57019080601f8301121561021c57813561526f816109d2565b9261527d604051948561057b565b81845260208085019260051b82010192831161021c57602001905b8282106152aa57505050608082015290565b8135815260209182019101615298565b6152c26151a6565b5081156153f5577f1f3b3aba000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000061531e6153188585614ff5565b90614f72565b16036153cb578161533a9261533292614fd8565b8101906151d4565b91806153b5575b61538b5763ffffffff615358835163ffffffff1690565b16116153615790565b7f2e2b0c290000000000000000000000000000000000000000000000000000000060005260046000fd5b7fee433e990000000000000000000000000000000000000000000000000000000060005260046000fd5b506153c661173b6040840151151590565b615341565b7f5247fdce0000000000000000000000000000000000000000000000000000000060005260046000fd5b7fb00b53dc0000000000000000000000000000000000000000000000000000000060005260046000fd5b907fffffffff0000000000000000000000000000000000000000000000000000000082167f2812d52c0000000000000000000000000000000000000000000000000000000081146154ed577f1e10bdc400000000000000000000000000000000000000000000000000000000146154e0577f2ee82075000000000000000000000000000000000000000000000000000000006000527fffffffff00000000000000000000000000000000000000000000000000000000821660045260246000fd5b61022c9150600190615717565b50905060208151036155265761550c6020825183010160208301615003565b6001600160a01b03811190811561555c575b506155265750565b613f77906040519182917f8d666f6000000000000000000000000000000000000000000000000000000000835260048301615706565b6104009150103861551e565b917fffffffff0000000000000000000000000000000000000000000000000000000083167f2812d52c000000000000000000000000000000000000000000000000000000008114615636577f1e10bdc40000000000000000000000000000000000000000000000000000000014615629577f2ee82075000000000000000000000000000000000000000000000000000000006000527fffffffff00000000000000000000000000000000000000000000000000000000831660045260246000fd5b61022c9250151590615717565b5050905060208151036155265761550c6020825183010160208301615003565b6001600160a01b036127099116600b615847565b6001600160a01b036127099116600b61597b565b9063ffffffff61569b93959495615693612ca3565b50169161503b565b918251116156bc57806156b0575b61538b5790565b506020810151156156a9565b7f4c4fc93a0000000000000000000000000000000000000000000000000000000060005260046000fd5b670de0b6b3a7640000916001600160e01b03615702921661268c565b0490565b9060206127099281815201906105bd565b90602082510361577f576157285750565b60208180518101031261021c576020810151156157425750565b613f77906040519182917fff828faa00000000000000000000000000000000000000000000000000000000835260206004840181815201906105bd565b6040517fff828faa0000000000000000000000000000000000000000000000000000000081526020600482015280613f7760248201856105bd565b80548210156127be5760005260206000200190600090565b916157ec918354906000199060031b92831b921b19161790565b9055565b8054801561581857600019019061580782826157ba565b60001982549160031b1b1916905555565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b60018101918060005282602052604060002054928315156000146158f95760001984018481116126675783549360001985019485116126675760009585836158aa9761589b95036158b0575b5050506157f0565b90600052602052604060002090565b55600190565b6158e06158da916158d16158c76158f095886157ba565b90549060031b1c90565b928391876157ba565b906157d2565b8590600052602052604060002090565b55388080615893565b50505050600090565b8054906801000000000000000082101561052257816159299160016157ec940181556157ba565b81939154906000199060031b92831b921b19161790565b6000818152600360205260409020546159755761595e816002615902565b600254906000526003602052604060002055600190565b50600090565b60008281526001820160205260409020546159b2578061599d83600193615902565b80549260005201602052604060002055600190565b5050600090565b6000818152600360205260409020549081156159b257600019820190828211612667576002549260001984019384116126675783836158aa9460009603615a19575b505050615a0860026157f0565b600390600052602052604060002090565b615a086158da91615a316158c7615a3b9560026157ba565b92839160026157ba565b553880806159fb56fea164736f6c634300081a000a", } var FeeQuoterABI = FeeQuoterMetaData.ABI diff --git a/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 7ee516ab47b..c063c5709f6 100644 --- a/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/ccip/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -6,7 +6,7 @@ ccip_encoding_utils: ../../../contracts/solc/ccip/EncodingUtils/EncodingUtils.so ccip_home: ../../../contracts/solc/ccip/CCIPHome/CCIPHome.sol/CCIPHome.abi.json ../../../contracts/solc/ccip/CCIPHome/CCIPHome.sol/CCIPHome.bin 39de1fbc907a2b573e9358e716803bf5ac3b0a2e622d5bc0069ab60daf38949b ccip_reader_tester: ../../../contracts/solc/ccip/CCIPReaderTester/CCIPReaderTester.sol/CCIPReaderTester.abi.json ../../../contracts/solc/ccip/CCIPReaderTester/CCIPReaderTester.sol/CCIPReaderTester.bin b8e597d175ec5ff4990d98b4e3b8a8cf06c6ae22977dd6f0e58c0f4107639e8f ether_sender_receiver: ../../../contracts/solc/ccip/EtherSenderReceiver/EtherSenderReceiver.sol/EtherSenderReceiver.abi.json ../../../contracts/solc/ccip/EtherSenderReceiver/EtherSenderReceiver.sol/EtherSenderReceiver.bin 88973abc1bfbca23a23704e20087ef46f2e20581a13477806308c8f2e664844e -fee_quoter: ../../../contracts/solc/ccip/FeeQuoter/FeeQuoter.sol/FeeQuoter.abi.json ../../../contracts/solc/ccip/FeeQuoter/FeeQuoter.sol/FeeQuoter.bin f1c4b4d170308bad29ca393d81202f3f5a40398385817023fdf4992f5ebeff8d +fee_quoter: ../../../contracts/solc/ccip/FeeQuoter/FeeQuoter.sol/FeeQuoter.abi.json ../../../contracts/solc/ccip/FeeQuoter/FeeQuoter.sol/FeeQuoter.bin 7be986f71fb72d1790b05033ba39531679284ff6a1b8f4978aea11763d932e73 lock_release_token_pool: ../../../contracts/solc/ccip/LockReleaseTokenPool/LockReleaseTokenPool.sol/LockReleaseTokenPool.abi.json ../../../contracts/solc/ccip/LockReleaseTokenPool/LockReleaseTokenPool.sol/LockReleaseTokenPool.bin 2e73ee0da6f9a9a5722294289b969e4202476706e5d7cdb623e728831c79c28b maybe_revert_message_receiver: ../../../contracts/solc/ccip/MaybeRevertMessageReceiver/MaybeRevertMessageReceiver.sol/MaybeRevertMessageReceiver.abi.json ../../../contracts/solc/ccip/MaybeRevertMessageReceiver/MaybeRevertMessageReceiver.sol/MaybeRevertMessageReceiver.bin d1eb951af1027ca20cbee2c34df80fddbfd861e1695989aeebd29327cfe56584 message_hasher: ../../../contracts/solc/ccip/MessageHasher/MessageHasher.sol/MessageHasher.abi.json ../../../contracts/solc/ccip/MessageHasher/MessageHasher.sol/MessageHasher.bin 9d503e62f007cfa7bf411ec845c3537b272709001d25ec738820ca83991c299c diff --git a/core/gethwrappers/generated/aggregator_v2v3_interface/aggregator_v2v3_interface.go b/core/gethwrappers/generated/aggregator_v2v3_interface/aggregator_v2v3_interface.go new file mode 100644 index 00000000000..e008e54fbb4 --- /dev/null +++ b/core/gethwrappers/generated/aggregator_v2v3_interface/aggregator_v2v3_interface.go @@ -0,0 +1,750 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package aggregator_v2v3_interface + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +var AggregatorV2V3InterfaceMetaData = &bind.MetaData{ + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"int256\",\"name\":\"current\",\"type\":\"int256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"updatedAt\",\"type\":\"uint256\"}],\"name\":\"AnswerUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"roundId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"startedBy\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"startedAt\",\"type\":\"uint256\"}],\"name\":\"NewRound\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"description\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundId\",\"type\":\"uint256\"}],\"name\":\"getAnswer\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint80\",\"name\":\"_roundId\",\"type\":\"uint80\"}],\"name\":\"getRoundData\",\"outputs\":[{\"internalType\":\"uint80\",\"name\":\"roundId\",\"type\":\"uint80\"},{\"internalType\":\"int256\",\"name\":\"answer\",\"type\":\"int256\"},{\"internalType\":\"uint256\",\"name\":\"startedAt\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"updatedAt\",\"type\":\"uint256\"},{\"internalType\":\"uint80\",\"name\":\"answeredInRound\",\"type\":\"uint80\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"roundId\",\"type\":\"uint256\"}],\"name\":\"getTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestAnswer\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestRound\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestRoundData\",\"outputs\":[{\"internalType\":\"uint80\",\"name\":\"roundId\",\"type\":\"uint80\"},{\"internalType\":\"int256\",\"name\":\"answer\",\"type\":\"int256\"},{\"internalType\":\"uint256\",\"name\":\"startedAt\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"updatedAt\",\"type\":\"uint256\"},{\"internalType\":\"uint80\",\"name\":\"answeredInRound\",\"type\":\"uint80\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", +} + +var AggregatorV2V3InterfaceABI = AggregatorV2V3InterfaceMetaData.ABI + +type AggregatorV2V3Interface struct { + address common.Address + abi abi.ABI + AggregatorV2V3InterfaceCaller + AggregatorV2V3InterfaceTransactor + AggregatorV2V3InterfaceFilterer +} + +type AggregatorV2V3InterfaceCaller struct { + contract *bind.BoundContract +} + +type AggregatorV2V3InterfaceTransactor struct { + contract *bind.BoundContract +} + +type AggregatorV2V3InterfaceFilterer struct { + contract *bind.BoundContract +} + +type AggregatorV2V3InterfaceSession struct { + Contract *AggregatorV2V3Interface + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type AggregatorV2V3InterfaceCallerSession struct { + Contract *AggregatorV2V3InterfaceCaller + CallOpts bind.CallOpts +} + +type AggregatorV2V3InterfaceTransactorSession struct { + Contract *AggregatorV2V3InterfaceTransactor + TransactOpts bind.TransactOpts +} + +type AggregatorV2V3InterfaceRaw struct { + Contract *AggregatorV2V3Interface +} + +type AggregatorV2V3InterfaceCallerRaw struct { + Contract *AggregatorV2V3InterfaceCaller +} + +type AggregatorV2V3InterfaceTransactorRaw struct { + Contract *AggregatorV2V3InterfaceTransactor +} + +func NewAggregatorV2V3Interface(address common.Address, backend bind.ContractBackend) (*AggregatorV2V3Interface, error) { + abi, err := abi.JSON(strings.NewReader(AggregatorV2V3InterfaceABI)) + if err != nil { + return nil, err + } + contract, err := bindAggregatorV2V3Interface(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &AggregatorV2V3Interface{address: address, abi: abi, AggregatorV2V3InterfaceCaller: AggregatorV2V3InterfaceCaller{contract: contract}, AggregatorV2V3InterfaceTransactor: AggregatorV2V3InterfaceTransactor{contract: contract}, AggregatorV2V3InterfaceFilterer: AggregatorV2V3InterfaceFilterer{contract: contract}}, nil +} + +func NewAggregatorV2V3InterfaceCaller(address common.Address, caller bind.ContractCaller) (*AggregatorV2V3InterfaceCaller, error) { + contract, err := bindAggregatorV2V3Interface(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &AggregatorV2V3InterfaceCaller{contract: contract}, nil +} + +func NewAggregatorV2V3InterfaceTransactor(address common.Address, transactor bind.ContractTransactor) (*AggregatorV2V3InterfaceTransactor, error) { + contract, err := bindAggregatorV2V3Interface(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &AggregatorV2V3InterfaceTransactor{contract: contract}, nil +} + +func NewAggregatorV2V3InterfaceFilterer(address common.Address, filterer bind.ContractFilterer) (*AggregatorV2V3InterfaceFilterer, error) { + contract, err := bindAggregatorV2V3Interface(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &AggregatorV2V3InterfaceFilterer{contract: contract}, nil +} + +func bindAggregatorV2V3Interface(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := AggregatorV2V3InterfaceMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _AggregatorV2V3Interface.Contract.AggregatorV2V3InterfaceCaller.contract.Call(opts, result, method, params...) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AggregatorV2V3Interface.Contract.AggregatorV2V3InterfaceTransactor.contract.Transfer(opts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _AggregatorV2V3Interface.Contract.AggregatorV2V3InterfaceTransactor.contract.Transact(opts, method, params...) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _AggregatorV2V3Interface.Contract.contract.Call(opts, result, method, params...) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _AggregatorV2V3Interface.Contract.contract.Transfer(opts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _AggregatorV2V3Interface.Contract.contract.Transact(opts, method, params...) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) Decimals(opts *bind.CallOpts) (uint8, error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "decimals") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) Decimals() (uint8, error) { + return _AggregatorV2V3Interface.Contract.Decimals(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) Decimals() (uint8, error) { + return _AggregatorV2V3Interface.Contract.Decimals(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) Description(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "description") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) Description() (string, error) { + return _AggregatorV2V3Interface.Contract.Description(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) Description() (string, error) { + return _AggregatorV2V3Interface.Contract.Description(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) GetAnswer(opts *bind.CallOpts, roundId *big.Int) (*big.Int, error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "getAnswer", roundId) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) GetAnswer(roundId *big.Int) (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.GetAnswer(&_AggregatorV2V3Interface.CallOpts, roundId) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) GetAnswer(roundId *big.Int) (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.GetAnswer(&_AggregatorV2V3Interface.CallOpts, roundId) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) GetRoundData(opts *bind.CallOpts, _roundId *big.Int) (GetRoundData, + + error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "getRoundData", _roundId) + + outstruct := new(GetRoundData) + if err != nil { + return *outstruct, err + } + + outstruct.RoundId = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + outstruct.Answer = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + outstruct.StartedAt = *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + outstruct.UpdatedAt = *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + outstruct.AnsweredInRound = *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) + + return *outstruct, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) GetRoundData(_roundId *big.Int) (GetRoundData, + + error) { + return _AggregatorV2V3Interface.Contract.GetRoundData(&_AggregatorV2V3Interface.CallOpts, _roundId) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) GetRoundData(_roundId *big.Int) (GetRoundData, + + error) { + return _AggregatorV2V3Interface.Contract.GetRoundData(&_AggregatorV2V3Interface.CallOpts, _roundId) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) GetTimestamp(opts *bind.CallOpts, roundId *big.Int) (*big.Int, error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "getTimestamp", roundId) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) GetTimestamp(roundId *big.Int) (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.GetTimestamp(&_AggregatorV2V3Interface.CallOpts, roundId) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) GetTimestamp(roundId *big.Int) (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.GetTimestamp(&_AggregatorV2V3Interface.CallOpts, roundId) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) LatestAnswer(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "latestAnswer") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) LatestAnswer() (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.LatestAnswer(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) LatestAnswer() (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.LatestAnswer(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) LatestRound(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "latestRound") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) LatestRound() (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.LatestRound(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) LatestRound() (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.LatestRound(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) LatestRoundData(opts *bind.CallOpts) (LatestRoundData, + + error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "latestRoundData") + + outstruct := new(LatestRoundData) + if err != nil { + return *outstruct, err + } + + outstruct.RoundId = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + outstruct.Answer = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + outstruct.StartedAt = *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + outstruct.UpdatedAt = *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + outstruct.AnsweredInRound = *abi.ConvertType(out[4], new(*big.Int)).(**big.Int) + + return *outstruct, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) LatestRoundData() (LatestRoundData, + + error) { + return _AggregatorV2V3Interface.Contract.LatestRoundData(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) LatestRoundData() (LatestRoundData, + + error) { + return _AggregatorV2V3Interface.Contract.LatestRoundData(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) LatestTimestamp(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "latestTimestamp") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) LatestTimestamp() (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.LatestTimestamp(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) LatestTimestamp() (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.LatestTimestamp(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCaller) Version(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _AggregatorV2V3Interface.contract.Call(opts, &out, "version") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceSession) Version() (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.Version(&_AggregatorV2V3Interface.CallOpts) +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceCallerSession) Version() (*big.Int, error) { + return _AggregatorV2V3Interface.Contract.Version(&_AggregatorV2V3Interface.CallOpts) +} + +type AggregatorV2V3InterfaceAnswerUpdatedIterator struct { + Event *AggregatorV2V3InterfaceAnswerUpdated + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *AggregatorV2V3InterfaceAnswerUpdatedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(AggregatorV2V3InterfaceAnswerUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(AggregatorV2V3InterfaceAnswerUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *AggregatorV2V3InterfaceAnswerUpdatedIterator) Error() error { + return it.fail +} + +func (it *AggregatorV2V3InterfaceAnswerUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type AggregatorV2V3InterfaceAnswerUpdated struct { + Current *big.Int + RoundId *big.Int + UpdatedAt *big.Int + Raw types.Log +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceFilterer) FilterAnswerUpdated(opts *bind.FilterOpts, current []*big.Int, roundId []*big.Int) (*AggregatorV2V3InterfaceAnswerUpdatedIterator, error) { + + var currentRule []interface{} + for _, currentItem := range current { + currentRule = append(currentRule, currentItem) + } + var roundIdRule []interface{} + for _, roundIdItem := range roundId { + roundIdRule = append(roundIdRule, roundIdItem) + } + + logs, sub, err := _AggregatorV2V3Interface.contract.FilterLogs(opts, "AnswerUpdated", currentRule, roundIdRule) + if err != nil { + return nil, err + } + return &AggregatorV2V3InterfaceAnswerUpdatedIterator{contract: _AggregatorV2V3Interface.contract, event: "AnswerUpdated", logs: logs, sub: sub}, nil +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceFilterer) WatchAnswerUpdated(opts *bind.WatchOpts, sink chan<- *AggregatorV2V3InterfaceAnswerUpdated, current []*big.Int, roundId []*big.Int) (event.Subscription, error) { + + var currentRule []interface{} + for _, currentItem := range current { + currentRule = append(currentRule, currentItem) + } + var roundIdRule []interface{} + for _, roundIdItem := range roundId { + roundIdRule = append(roundIdRule, roundIdItem) + } + + logs, sub, err := _AggregatorV2V3Interface.contract.WatchLogs(opts, "AnswerUpdated", currentRule, roundIdRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(AggregatorV2V3InterfaceAnswerUpdated) + if err := _AggregatorV2V3Interface.contract.UnpackLog(event, "AnswerUpdated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceFilterer) ParseAnswerUpdated(log types.Log) (*AggregatorV2V3InterfaceAnswerUpdated, error) { + event := new(AggregatorV2V3InterfaceAnswerUpdated) + if err := _AggregatorV2V3Interface.contract.UnpackLog(event, "AnswerUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type AggregatorV2V3InterfaceNewRoundIterator struct { + Event *AggregatorV2V3InterfaceNewRound + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *AggregatorV2V3InterfaceNewRoundIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(AggregatorV2V3InterfaceNewRound) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(AggregatorV2V3InterfaceNewRound) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *AggregatorV2V3InterfaceNewRoundIterator) Error() error { + return it.fail +} + +func (it *AggregatorV2V3InterfaceNewRoundIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type AggregatorV2V3InterfaceNewRound struct { + RoundId *big.Int + StartedBy common.Address + StartedAt *big.Int + Raw types.Log +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceFilterer) FilterNewRound(opts *bind.FilterOpts, roundId []*big.Int, startedBy []common.Address) (*AggregatorV2V3InterfaceNewRoundIterator, error) { + + var roundIdRule []interface{} + for _, roundIdItem := range roundId { + roundIdRule = append(roundIdRule, roundIdItem) + } + var startedByRule []interface{} + for _, startedByItem := range startedBy { + startedByRule = append(startedByRule, startedByItem) + } + + logs, sub, err := _AggregatorV2V3Interface.contract.FilterLogs(opts, "NewRound", roundIdRule, startedByRule) + if err != nil { + return nil, err + } + return &AggregatorV2V3InterfaceNewRoundIterator{contract: _AggregatorV2V3Interface.contract, event: "NewRound", logs: logs, sub: sub}, nil +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceFilterer) WatchNewRound(opts *bind.WatchOpts, sink chan<- *AggregatorV2V3InterfaceNewRound, roundId []*big.Int, startedBy []common.Address) (event.Subscription, error) { + + var roundIdRule []interface{} + for _, roundIdItem := range roundId { + roundIdRule = append(roundIdRule, roundIdItem) + } + var startedByRule []interface{} + for _, startedByItem := range startedBy { + startedByRule = append(startedByRule, startedByItem) + } + + logs, sub, err := _AggregatorV2V3Interface.contract.WatchLogs(opts, "NewRound", roundIdRule, startedByRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(AggregatorV2V3InterfaceNewRound) + if err := _AggregatorV2V3Interface.contract.UnpackLog(event, "NewRound", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_AggregatorV2V3Interface *AggregatorV2V3InterfaceFilterer) ParseNewRound(log types.Log) (*AggregatorV2V3InterfaceNewRound, error) { + event := new(AggregatorV2V3InterfaceNewRound) + if err := _AggregatorV2V3Interface.contract.UnpackLog(event, "NewRound", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type GetRoundData struct { + RoundId *big.Int + Answer *big.Int + StartedAt *big.Int + UpdatedAt *big.Int + AnsweredInRound *big.Int +} +type LatestRoundData struct { + RoundId *big.Int + Answer *big.Int + StartedAt *big.Int + UpdatedAt *big.Int + AnsweredInRound *big.Int +} + +func (_AggregatorV2V3Interface *AggregatorV2V3Interface) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _AggregatorV2V3Interface.abi.Events["AnswerUpdated"].ID: + return _AggregatorV2V3Interface.ParseAnswerUpdated(log) + case _AggregatorV2V3Interface.abi.Events["NewRound"].ID: + return _AggregatorV2V3Interface.ParseNewRound(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (AggregatorV2V3InterfaceAnswerUpdated) Topic() common.Hash { + return common.HexToHash("0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f") +} + +func (AggregatorV2V3InterfaceNewRound) Topic() common.Hash { + return common.HexToHash("0x0109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271") +} + +func (_AggregatorV2V3Interface *AggregatorV2V3Interface) Address() common.Address { + return _AggregatorV2V3Interface.address +} + +type AggregatorV2V3InterfaceInterface interface { + Decimals(opts *bind.CallOpts) (uint8, error) + + Description(opts *bind.CallOpts) (string, error) + + GetAnswer(opts *bind.CallOpts, roundId *big.Int) (*big.Int, error) + + GetRoundData(opts *bind.CallOpts, _roundId *big.Int) (GetRoundData, + + error) + + GetTimestamp(opts *bind.CallOpts, roundId *big.Int) (*big.Int, error) + + LatestAnswer(opts *bind.CallOpts) (*big.Int, error) + + LatestRound(opts *bind.CallOpts) (*big.Int, error) + + LatestRoundData(opts *bind.CallOpts) (LatestRoundData, + + error) + + LatestTimestamp(opts *bind.CallOpts) (*big.Int, error) + + Version(opts *bind.CallOpts) (*big.Int, error) + + FilterAnswerUpdated(opts *bind.FilterOpts, current []*big.Int, roundId []*big.Int) (*AggregatorV2V3InterfaceAnswerUpdatedIterator, error) + + WatchAnswerUpdated(opts *bind.WatchOpts, sink chan<- *AggregatorV2V3InterfaceAnswerUpdated, current []*big.Int, roundId []*big.Int) (event.Subscription, error) + + ParseAnswerUpdated(log types.Log) (*AggregatorV2V3InterfaceAnswerUpdated, error) + + FilterNewRound(opts *bind.FilterOpts, roundId []*big.Int, startedBy []common.Address) (*AggregatorV2V3InterfaceNewRoundIterator, error) + + WatchNewRound(opts *bind.WatchOpts, sink chan<- *AggregatorV2V3InterfaceNewRound, roundId []*big.Int, startedBy []common.Address) (event.Subscription, error) + + ParseNewRound(log types.Log) (*AggregatorV2V3InterfaceNewRound, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index 03ebf78995e..7080ec31b26 100644 --- a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -1,4 +1,5 @@ GETH_VERSION: 1.14.11 +aggregator_v2v3_interface: ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.bin 95e8814b408bb05bf21742ef580d98698b7db6a9bac6a35c3de12b23aec4ee28 arbitrum_module: ../../contracts/solc/v0.8.19/ArbitrumModule/ArbitrumModule.abi ../../contracts/solc/v0.8.19/ArbitrumModule/ArbitrumModule.bin 12a7bad1f887d832d101a73ae279a91a90c93fd72befea9983e85eff493f62f4 authorized_forwarder: ../../contracts/solc/v0.8.19/AuthorizedForwarder/AuthorizedForwarder.abi ../../contracts/solc/v0.8.19/AuthorizedForwarder/AuthorizedForwarder.bin 8ea76c883d460f8353a45a493f2aebeb5a2d9a7b4619d1bc4fff5fb590bb3e10 authorized_receiver: ../../contracts/solc/v0.8.19/AuthorizedReceiver/AuthorizedReceiver.abi ../../contracts/solc/v0.8.19/AuthorizedReceiver/AuthorizedReceiver.bin 18e8969ba3234b027e1b16c11a783aca58d0ea5c2361010ec597f134b7bf1c4f diff --git a/core/gethwrappers/go_generate.go b/core/gethwrappers/go_generate.go index ab610f01d67..7f1d3f02e91 100644 --- a/core/gethwrappers/go_generate.go +++ b/core/gethwrappers/go_generate.go @@ -145,6 +145,7 @@ package gethwrappers //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/VRFCoordinatorTestV2_5/VRFCoordinatorTestV2_5.abi ../../contracts/solc/v0.8.19/VRFCoordinatorTestV2_5/VRFCoordinatorTestV2_5.bin VRFCoordinatorTestV2_5 vrf_coordinator_test_v2_5 // Aggregators +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.abi ../../contracts/solc/v0.8.6/AggregatorV2V3Interface/AggregatorV2V3Interface.bin AggregatorV2V3Interface aggregator_v2v3_interface //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.6/MockAggregatorProxy/MockAggregatorProxy.abi ../../contracts/solc/v0.8.6/MockAggregatorProxy/MockAggregatorProxy.bin MockAggregatorProxy mock_aggregator_proxy //go:generate go generate ./functions diff --git a/core/scripts/go.mod b/core/scripts/go.mod index c12f1dd1db2..b290bce85e6 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -311,15 +311,15 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix // indirect github.com/smartcontractkit/chain-selectors v1.0.36 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250103152858-8973fd0c912b // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616-a2ea5e50b260 // indirect github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20241009055228-33d0c0bf38de // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 81a3a412d2a..dce110fd18c 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1160,8 +1160,8 @@ github.com/smartcontractkit/chain-selectors v1.0.36 h1:KSpO8I+JOiuyN4FuXsV471sPo github.com/smartcontractkit/chain-selectors v1.0.36/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 h1:TeWnxdgSO2cYCKcBMwdkRcs/YdhSvXoWqqw7QWz/KeI= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103/go.mod h1:ncjd6mPZSRlelEqH/2KeLE1pU3UlqzBSn8RYkEoECzY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd h1:1xtdmHoleuTNxXlcRAZ7MslnKATwDDWOZfdcBuaN2SE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd/go.mod h1:JJZMCB75aVSAiPNW032F9WUKTlLztTd8bbQB5MEaZa4= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250103152858-8973fd0c912b h1:UBXi9Yj8YSMHDDaxQLu273x1fWjyEL9xP58nuJsqZfg= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250103152858-8973fd0c912b/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4 h1:cf7mgbR8OelUnq49x0vYLy1XWddw4t1Q1YsBPxUQY4M= @@ -1178,10 +1178,10 @@ github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 h1:0ewLMbAz3 github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 h1:L6UDu0GzSKfz+/nMSz9yfYrgOpW1/OXhiVK60PxVsnY= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3/go.mod h1:52U0UH8K0Qwu+HB1LMc+5V27ru2Tgy29YPT+2HkMevY= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 h1:kDW6Ab8vGRK2y+DPEvvhU2It8UCS9FK5ZQqIVjDfpK4= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5/go.mod h1:uHVnYLMgJ1rTcNoVxhBpy38t69gXq0j+LN3TkcIVE3U= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 h1:tQCjnIjY88AClWXApaTS+/ihQYM1GVCrbD9W00eh11E= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34/go.mod h1:lgG9JT2P19KnYuBheKIis5ZeCO+AaSta+RfzvwDQS2Y= github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.2-0.20250110073248-456673e8eea2 h1:nTUoe7GZLw17nPLV5t3Vgf4U4pf+VW0Uko5xpNiKdKU= github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.2-0.20250110073248-456673e8eea2/go.mod h1:mMUqvS3BZfvN1OfK4OFTYf1+T0X6nwmSXJM2keaPsSM= github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 h1:T0kbw07Vb6xUyA9MIJZfErMgWseWi1zf7cYvRpoq7ug= diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 06289b13fd4..50921cc93cc 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -818,7 +818,7 @@ func (app *ChainlinkApplication) Start(ctx context.Context) error { return multierr.Combine(err, ms.Close()) } - app.logger.Debugw("Starting service...", "name", service.Name()) + app.logger.Infow("Starting service...", "name", service.Name()) if err := ms.Start(ctx, service); err != nil { return err diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 0e64fa0444e..764830409fc 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -664,6 +664,9 @@ func TestConfig_Marshal(t *testing.T) { AutoPurge: evmcfg.AutoPurgeConfig{ Enabled: ptr(false), }, + TransactionManagerV2: evmcfg.TransactionManagerV2Config{ + Enabled: ptr(false), + }, }, HeadTracker: evmcfg.HeadTracker{ @@ -1132,6 +1135,9 @@ ResendAfterThreshold = '1h0m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true @@ -1408,6 +1414,15 @@ func TestConfig_full(t *testing.T) { got.EVM[c].Nodes[n].Order = ptr(int32(100)) } } + if got.EVM[c].Transactions.TransactionManagerV2.BlockTime == nil { + got.EVM[c].Transactions.TransactionManagerV2.BlockTime = new(commoncfg.Duration) + } + if got.EVM[c].Transactions.TransactionManagerV2.CustomURL == nil { + got.EVM[c].Transactions.TransactionManagerV2.CustomURL = new(commoncfg.URL) + } + if got.EVM[c].Transactions.TransactionManagerV2.DualBroadcast == nil { + got.EVM[c].Transactions.TransactionManagerV2.DualBroadcast = ptr(false) + } if got.EVM[c].Transactions.AutoPurge.Threshold == nil { got.EVM[c].Transactions.AutoPurge.Threshold = ptr(uint32(0)) } diff --git a/core/services/chainlink/testdata/config-full.toml b/core/services/chainlink/testdata/config-full.toml index a3fcea9bf73..b57f9a76cf2 100644 --- a/core/services/chainlink/testdata/config-full.toml +++ b/core/services/chainlink/testdata/config-full.toml @@ -349,6 +349,9 @@ ResendAfterThreshold = '1h0m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/core/services/chainlink/testdata/config-invalid.toml b/core/services/chainlink/testdata/config-invalid.toml index 347530cec53..2a3506da45b 100644 --- a/core/services/chainlink/testdata/config-invalid.toml +++ b/core/services/chainlink/testdata/config-invalid.toml @@ -58,6 +58,9 @@ FinalizedBlockOffset = 64 [EVM.Transactions.AutoPurge] Enabled = true +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.GasEstimator] Mode = 'FixedPrice' BumpThreshold = 0 @@ -112,6 +115,9 @@ ChainType = 'scroll' Enabled = true DetectionApiUrl = '' +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [[EVM.Nodes]] Name = 'scroll node' WSURL = 'ws://foo.bar' diff --git a/core/services/chainlink/testdata/config-multi-chain-effective.toml b/core/services/chainlink/testdata/config-multi-chain-effective.toml index a659223134a..dbaafbe67d1 100644 --- a/core/services/chainlink/testdata/config-multi-chain-effective.toml +++ b/core/services/chainlink/testdata/config-multi-chain-effective.toml @@ -332,6 +332,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true @@ -443,6 +446,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true @@ -548,6 +554,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/core/web/resolver/testdata/config-full.toml b/core/web/resolver/testdata/config-full.toml index 4672d176915..d2d4b90e3ca 100644 --- a/core/web/resolver/testdata/config-full.toml +++ b/core/web/resolver/testdata/config-full.toml @@ -349,6 +349,9 @@ ResendAfterThreshold = '1h0m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/core/web/resolver/testdata/config-multi-chain-effective.toml b/core/web/resolver/testdata/config-multi-chain-effective.toml index 35974ea1ac8..82b82216371 100644 --- a/core/web/resolver/testdata/config-multi-chain-effective.toml +++ b/core/web/resolver/testdata/config-multi-chain-effective.toml @@ -332,6 +332,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true @@ -443,6 +446,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true @@ -548,6 +554,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/deployment/ccip/changeset/cs_ccip_home.go b/deployment/ccip/changeset/cs_ccip_home.go index ef173e5ed07..ffd353e7d59 100644 --- a/deployment/ccip/changeset/cs_ccip_home.go +++ b/deployment/ccip/changeset/cs_ccip_home.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "math/big" - "os" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -88,7 +87,12 @@ func validateCommitOffchainConfig(c *pluginconfig.CommitOffchainConfig, selector for _, tk := range onchainState.ERC677Tokens { tokenInfos = append(tokenInfos, tk) } - tokenInfos = append(tokenInfos, onchainState.LinkToken) + var linkTokenInfo tokenInfo + linkTokenInfo = onchainState.LinkToken + if onchainState.LinkToken == nil { + linkTokenInfo = onchainState.StaticLinkToken + } + tokenInfos = append(tokenInfos, linkTokenInfo) tokenInfos = append(tokenInfos, onchainState.Weth9) symbol, decimal, err := findTokenInfo(tokenInfos, token) if err != nil { @@ -193,7 +197,7 @@ func WithDefaultCommitOffChainConfig(feedChainSel uint64, tokenInfo map[ccipocr3 PriceFeedChainSelector: ccipocr3.ChainSelector(feedChainSel), NewMsgScanBatchSize: merklemulti.MaxNumberTreeLeaves, MaxReportTransmissionCheckAttempts: 5, - RMNEnabled: os.Getenv("ENABLE_RMN") == "true", // only enabled in manual test + RMNEnabled: false, RMNSignaturesTimeout: 30 * time.Minute, MaxMerkleTreeSize: merklemulti.MaxNumberTreeLeaves, SignObservationPrefix: "chainlink ccip 1.6 rmn observation", diff --git a/deployment/ccip/changeset/cs_chain_contracts.go b/deployment/ccip/changeset/cs_chain_contracts.go index 64e7ca295d7..e52c2f2a603 100644 --- a/deployment/ccip/changeset/cs_chain_contracts.go +++ b/deployment/ccip/changeset/cs_chain_contracts.go @@ -157,9 +157,6 @@ func UpdateNonceManagersChangeset(e deployment.Environment, cfg UpdateNonceManag if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("error updating previous ramps for chain %s: %w", e.Chains[chainSel].String(), err) } - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("error updating previous ramps for chain %s: %w", e.Chains[chainSel].String(), err) - } } if cfg.MCMS == nil { if authTx != nil { @@ -219,6 +216,7 @@ func UpdateNonceManagersChangeset(e deployment.Environment, cfg UpdateNonceManag } type UpdateOnRampDestsConfig struct { + // UpdatesByChain is a mapping of source -> dest -> update. UpdatesByChain map[uint64]map[uint64]OnRampDestinationUpdate // Disallow mixing MCMS/non-MCMS per chain for simplicity. // (can still be achieved by calling this function multiple times) @@ -490,6 +488,8 @@ func UpdateFeeQuoterPricesChangeset(e deployment.Environment, cfg UpdateFeeQuote }, }, }) + timelocks[chainSel] = s.Chains[chainSel].Timelock.Address() + proposers[chainSel] = s.Chains[chainSel].ProposerMcm } } if cfg.MCMS == nil { @@ -512,6 +512,7 @@ func UpdateFeeQuoterPricesChangeset(e deployment.Environment, cfg UpdateFeeQuote } type UpdateFeeQuoterDestsConfig struct { + // UpdatesByChain is a mapping from source -> dest -> config update. UpdatesByChain map[uint64]map[uint64]fee_quoter.FeeQuoterDestChainConfig // Disallow mixing MCMS/non-MCMS per chain for simplicity. // (can still be achieved by calling this function multiple times) @@ -627,6 +628,8 @@ func UpdateFeeQuoterDestsChangeset(e deployment.Environment, cfg UpdateFeeQuoter } type UpdateOffRampSourcesConfig struct { + // UpdatesByChain is a mapping from dest chain -> source chain -> source chain + // update on the dest chain offramp. UpdatesByChain map[uint64]map[uint64]OffRampSourceUpdate MCMS *MCMSConfig } diff --git a/deployment/ccip/changeset/cs_deploy_chain.go b/deployment/ccip/changeset/cs_deploy_chain.go index 0cdc2327ca3..28a4702df0b 100644 --- a/deployment/ccip/changeset/cs_deploy_chain.go +++ b/deployment/ccip/changeset/cs_deploy_chain.go @@ -337,7 +337,7 @@ func deployChainContracts( }, onramp.OnRampDynamicConfig{ FeeQuoter: feeQuoterContract.Address(), - FeeAggregator: common.HexToAddress("0x1"), // TODO real fee aggregator + FeeAggregator: chain.DeployerKey.From, // TODO real fee aggregator, using deployer key for now }, []onramp.OnRampDestChainConfigArgs{}, ) diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index b1f44ac4b99..323226c6a4a 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -584,7 +584,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type if err != nil { return state, err } - key, ok := MockDescriptionToTokenSymbol[desc] + key, ok := DescriptionToTokenSymbol[desc] if !ok { return state, fmt.Errorf("unknown feed description %s", desc) } diff --git a/deployment/ccip/changeset/testhelpers/test_environment.go b/deployment/ccip/changeset/testhelpers/test_environment.go index 040dce9f17f..ca58a067667 100644 --- a/deployment/ccip/changeset/testhelpers/test_environment.go +++ b/deployment/ccip/changeset/testhelpers/test_environment.go @@ -459,7 +459,7 @@ func NewEnvironmentWithJobsAndContracts(t *testing.T, tEnv TestEnvironment) Depl }) require.NoError(t, err) tEnv.UpdateDeployedEnvironment(e) - e = AddCCIPContractsToEnvironment(t, e.Env.AllChainSelectors(), tEnv) + e = AddCCIPContractsToEnvironment(t, e.Env.AllChainSelectors(), tEnv, true, true, false) // now we update RMNProxy to point to RMNRemote e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ { @@ -473,15 +473,17 @@ func NewEnvironmentWithJobsAndContracts(t *testing.T, tEnv TestEnvironment) Depl return e } -func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEnvironment) DeployedEnv { +func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEnvironment, deployJobs, deployHomeChain, mcmsEnabled bool) DeployedEnv { tc := tEnv.TestConfigs() e := tEnv.DeployedEnvironment() envNodes, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain) require.NoError(t, err) + // Need to deploy prerequisites first so that we can form the USDC config // no proposals to be made, timelock can be passed as nil here - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ - { + var apps []commonchangeset.ChangesetApplication + if deployHomeChain { + apps = append(apps, commonchangeset.ChangesetApplication{ Changeset: commonchangeset.WrapChangeSet(changeset.DeployHomeChainChangeset), Config: changeset.DeployHomeChainConfig{ HomeChainSel: e.HomeChainSel, @@ -492,15 +494,16 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn TestNodeOperator: envNodes.NonBootstraps().PeerIDs(), }, }, - }, - { - Changeset: commonchangeset.WrapChangeSet(changeset.DeployChainContractsChangeset), - Config: changeset.DeployChainContractsConfig{ - ChainSelectors: allChains, - HomeChainSelector: e.HomeChainSel, - }, + }) + } + apps = append(apps, commonchangeset.ChangesetApplication{ + Changeset: commonchangeset.WrapChangeSet(changeset.DeployChainContractsChangeset), + Config: changeset.DeployChainContractsConfig{ + ChainSelectors: allChains, + HomeChainSelector: e.HomeChainSel, }, }) + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, apps) require.NoError(t, err) state, err := changeset.LoadOnchainState(e.Env) @@ -545,9 +548,19 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn CallProxy: state.Chains[chain].CallProxy, } tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - ocrParams := changeset.DeriveCCIPOCRParams(changeset.WithDefaultCommitOffChainConfig(e.FeedChainSel, tokenInfo), + ocrOverride := tc.OCRConfigOverride + if tc.RMNEnabled { + ocrOverride = func(ocrParams *changeset.CCIPOCRParams) { + if tc.OCRConfigOverride != nil { + tc.OCRConfigOverride(ocrParams) + } + ocrParams.CommitOffChainConfig.RMNEnabled = true + } + } + ocrParams := changeset.DeriveCCIPOCRParams( + changeset.WithDefaultCommitOffChainConfig(e.FeedChainSel, tokenInfo), changeset.WithDefaultExecuteOffChainConfig(tokenDataProviders), - changeset.WithOCRParamOverride(tc.OCRConfigOverride), + changeset.WithOCRParamOverride(ocrOverride), ) ocrConfigs[chain] = ocrParams chainConfigs[chain] = changeset.ChainConfig{ @@ -560,14 +573,25 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn }, } } - // Deploy second set of changesets to deploy and configure the CCIP contracts. - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelockContractsPerChain, []commonchangeset.ChangesetApplication{ + timelockContractsPerChain[e.HomeChainSel] = &proposalutils.TimelockExecutionContracts{ + Timelock: state.Chains[e.HomeChainSel].Timelock, + CallProxy: state.Chains[e.HomeChainSel].CallProxy, + } + // Apply second set of changesets to configure the CCIP contracts. + var mcmsConfig *changeset.MCMSConfig + if mcmsEnabled { + mcmsConfig = &changeset.MCMSConfig{ + MinDelay: 0, + } + } + apps = []commonchangeset.ChangesetApplication{ { // Add the chain configs for the new chains. Changeset: commonchangeset.WrapChangeSet(changeset.UpdateChainConfigChangeset), Config: changeset.UpdateChainConfigConfig{ HomeChainSelector: e.HomeChainSel, RemoteChainAdds: chainConfigs, + MCMS: mcmsConfig, }, }, { @@ -577,6 +601,7 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn SetCandidateConfigBase: changeset.SetCandidateConfigBase{ HomeChainSelector: e.HomeChainSel, FeedChainSelector: e.FeedChainSel, + MCMS: mcmsConfig, }, PluginInfo: changeset.SetCandidatePluginInfo{ OCRConfigPerRemoteChainSelector: ocrConfigs, @@ -591,6 +616,7 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn SetCandidateConfigBase: changeset.SetCandidateConfigBase{ HomeChainSelector: e.HomeChainSel, FeedChainSelector: e.FeedChainSel, + MCMS: mcmsConfig, }, PluginInfo: []changeset.SetCandidatePluginInfo{ { @@ -615,6 +641,7 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn RemoteChainSelectors: allChains, }, }, + MCMS: mcmsConfig, }, }, { @@ -625,10 +652,13 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn RemoteChainSels: allChains, }, }, - { + } + if deployJobs { + apps = append(apps, commonchangeset.ChangesetApplication{ Changeset: commonchangeset.WrapChangeSet(changeset.CCIPCapabilityJobspecChangeset), - }, - }) + }) + } + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelockContractsPerChain, apps) require.NoError(t, err) ReplayLogs(t, e.Env.Offchain, e.ReplayBlocks) diff --git a/deployment/ccip/changeset/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index fc08dc3c6b3..dcfba1fad9e 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -360,7 +360,7 @@ func DoSendRequest( } require.True(t, it.Next()) - t.Logf("CCIP message (id %x) sent from chain selector %d to chain selector %d tx %s seqNum %d nonce %d sender %s", + t.Logf("CCIP message (id %x) sent from chain selector %d to chain selector %d tx %s seqNum %d nonce %d sender %s testRouterEnabled %t", it.Event.Message.Header.MessageId[:], cfg.SourceChain, cfg.DestChain, @@ -368,6 +368,7 @@ func DoSendRequest( it.Event.SequenceNumber, it.Event.Message.Header.Nonce, it.Event.Message.Sender.String(), + cfg.IsTestRouter, ) return it.Event, nil } @@ -397,7 +398,15 @@ func MakeEVMExtraArgsV2(gasLimit uint64, allowOOO bool) []byte { return extraArgs } -func AddLane(t *testing.T, e *DeployedEnv, from, to uint64, isTestRouter bool, gasprice map[uint64]*big.Int, tokenPrices map[common.Address]*big.Int, fqCfg fee_quoter.FeeQuoterDestChainConfig) { +func AddLane( + t *testing.T, + e *DeployedEnv, + from, to uint64, + isTestRouter bool, + gasprice map[uint64]*big.Int, + tokenPrices map[common.Address]*big.Int, + fqCfg fee_quoter.FeeQuoterDestChainConfig, +) { var err error e.Env, err = commoncs.ApplyChangesets(t, e.Env, e.TimelockContracts(t), []commoncs.ChangesetApplication{ { @@ -474,7 +483,11 @@ func AddLane(t *testing.T, e *DeployedEnv, from, to uint64, isTestRouter bool, g func AddLaneWithDefaultPricesAndFeeQuoterConfig(t *testing.T, e *DeployedEnv, state changeset.CCIPOnChainState, from, to uint64, isTestRouter bool) { stateChainFrom := state.Chains[from] - AddLane(t, e, from, to, isTestRouter, + AddLane( + t, + e, + from, to, + isTestRouter, map[uint64]*big.Int{ to: DefaultGasPrice, }, map[common.Address]*big.Int{ diff --git a/deployment/ccip/changeset/token_info.go b/deployment/ccip/changeset/token_info.go index 379119dba92..cecc3070ab8 100644 --- a/deployment/ccip/changeset/token_info.go +++ b/deployment/ccip/changeset/token_info.go @@ -20,11 +20,18 @@ type TokenSymbol string const ( LinkSymbol TokenSymbol = "LINK" WethSymbol TokenSymbol = "WETH" + WAVAXSymbol TokenSymbol = "WAVAX" USDCSymbol TokenSymbol = "USDC" USDCName string = "USD Coin" LinkDecimals = 18 WethDecimals = 18 UsdcDecimals = 6 + + // Price Feed Descriptions + AvaxUSD = "AVAX / USD" + LinkUSD = "LINK / USD" + EthUSD = "ETH / USD" + // MockLinkAggregatorDescription is the description of the MockV3Aggregator.sol contract // https://github.com/smartcontractkit/chainlink/blob/a348b98e90527520049c580000a86fb8ceff7fa7/contracts/src/v0.8/tests/MockV3Aggregator.sol#L76-L76 MockLinkAggregatorDescription = "v0.8/tests/MockV3Aggregator.sol" @@ -36,10 +43,13 @@ const ( var ( MockLinkPrice = deployment.E18Mult(500) MockWethPrice = big.NewInt(9e8) - // MockDescriptionToTokenSymbol maps a mock feed description to token descriptor - MockDescriptionToTokenSymbol = map[string]TokenSymbol{ + // DescriptionToTokenSymbol maps price feed description to token descriptor + DescriptionToTokenSymbol = map[string]TokenSymbol{ MockLinkAggregatorDescription: LinkSymbol, MockWETHAggregatorDescription: WethSymbol, + LinkUSD: LinkSymbol, + AvaxUSD: WAVAXSymbol, + EthUSD: WethSymbol, } MockSymbolToDescription = map[TokenSymbol]string{ LinkSymbol: MockLinkAggregatorDescription, diff --git a/deployment/environment_test.go b/deployment/environment_test.go index 37a7e3baeae..5ebbe0be1ce 100644 --- a/deployment/environment_test.go +++ b/deployment/environment_test.go @@ -9,16 +9,16 @@ import ( func TestNode_OCRConfigForChainSelector(t *testing.T) { var m = map[chain_selectors.ChainDetails]OCRConfig{ - chain_selectors.ChainDetails{ + { ChainSelector: chain_selectors.APTOS_TESTNET.Selector, ChainName: chain_selectors.APTOS_TESTNET.Name, - }: OCRConfig{ + }: { KeyBundleID: "aptos bundle 1", }, - chain_selectors.ChainDetails{ + { ChainSelector: chain_selectors.ETHEREUM_MAINNET_ARBITRUM_1.Selector, ChainName: chain_selectors.ETHEREUM_MAINNET_ARBITRUM_1.Name, - }: OCRConfig{ + }: { KeyBundleID: "arb bundle 1", }, } diff --git a/deployment/go.mod b/deployment/go.mod index 333165fa2b1..c7b85236710 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -29,11 +29,11 @@ require ( github.com/sethvargo/go-retry v0.2.4 github.com/smartcontractkit/ccip-owner-contracts v0.0.0-salt-fix github.com/smartcontractkit/chain-selectors v1.0.36 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250103152858-8973fd0c912b github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4 github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.2-0.20250110073248-456673e8eea2 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 github.com/smartcontractkit/libocr v0.0.0-20241223215956-e5b78d8e3919 @@ -419,7 +419,7 @@ require ( github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616-a2ea5e50b260 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.5 // indirect github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.2 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index a6dceb08171..9ef8a297529 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1386,8 +1386,8 @@ github.com/smartcontractkit/chain-selectors v1.0.36 h1:KSpO8I+JOiuyN4FuXsV471sPo github.com/smartcontractkit/chain-selectors v1.0.36/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 h1:TeWnxdgSO2cYCKcBMwdkRcs/YdhSvXoWqqw7QWz/KeI= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103/go.mod h1:ncjd6mPZSRlelEqH/2KeLE1pU3UlqzBSn8RYkEoECzY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd h1:1xtdmHoleuTNxXlcRAZ7MslnKATwDDWOZfdcBuaN2SE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd/go.mod h1:JJZMCB75aVSAiPNW032F9WUKTlLztTd8bbQB5MEaZa4= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250103152858-8973fd0c912b h1:UBXi9Yj8YSMHDDaxQLu273x1fWjyEL9xP58nuJsqZfg= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250103152858-8973fd0c912b/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4 h1:cf7mgbR8OelUnq49x0vYLy1XWddw4t1Q1YsBPxUQY4M= @@ -1404,10 +1404,10 @@ github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 h1:0ewLMbAz3 github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 h1:L6UDu0GzSKfz+/nMSz9yfYrgOpW1/OXhiVK60PxVsnY= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3/go.mod h1:52U0UH8K0Qwu+HB1LMc+5V27ru2Tgy29YPT+2HkMevY= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 h1:kDW6Ab8vGRK2y+DPEvvhU2It8UCS9FK5ZQqIVjDfpK4= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5/go.mod h1:uHVnYLMgJ1rTcNoVxhBpy38t69gXq0j+LN3TkcIVE3U= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 h1:tQCjnIjY88AClWXApaTS+/ihQYM1GVCrbD9W00eh11E= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34/go.mod h1:lgG9JT2P19KnYuBheKIis5ZeCO+AaSta+RfzvwDQS2Y= github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.2-0.20250110073248-456673e8eea2 h1:nTUoe7GZLw17nPLV5t3Vgf4U4pf+VW0Uko5xpNiKdKU= github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.2-0.20250110073248-456673e8eea2/go.mod h1:mMUqvS3BZfvN1OfK4OFTYf1+T0X6nwmSXJM2keaPsSM= github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 h1:T0kbw07Vb6xUyA9MIJZfErMgWseWi1zf7cYvRpoq7ug= diff --git a/deployment/multiclient.go b/deployment/multiclient.go index 9765e0368ea..256de787b5e 100644 --- a/deployment/multiclient.go +++ b/deployment/multiclient.go @@ -7,6 +7,7 @@ import ( "time" "github.com/avast/retry-go/v4" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -88,6 +89,26 @@ func (mc *MultiClient) SendTransaction(ctx context.Context, tx *types.Transactio }) } +func (mc *MultiClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + var result []byte + err := mc.retryWithBackups("CallContract", func(client *ethclient.Client) error { + var err error + result, err = client.CallContract(ctx, msg, blockNumber) + return err + }) + return result, err +} + +func (mc *MultiClient) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { + var result []byte + err := mc.retryWithBackups("CallContractAtHash", func(client *ethclient.Client) error { + var err error + result, err = client.CallContractAtHash(ctx, msg, blockHash) + return err + }) + return result, err +} + func (mc *MultiClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { var code []byte err := mc.retryWithBackups("CodeAt", func(client *ethclient.Client) error { @@ -98,6 +119,16 @@ func (mc *MultiClient) CodeAt(ctx context.Context, account common.Address, block return code, err } +func (mc *MultiClient) CodeAtHash(ctx context.Context, account common.Address, blockHash common.Hash) ([]byte, error) { + var code []byte + err := mc.retryWithBackups("CodeAtHash", func(client *ethclient.Client) error { + var err error + code, err = client.CodeAtHash(ctx, account, blockHash) + return err + }) + return code, err +} + func (mc *MultiClient) NonceAt(ctx context.Context, account common.Address, block *big.Int) (uint64, error) { var count uint64 err := mc.retryWithBackups("NonceAt", func(client *ethclient.Client) error { @@ -108,6 +139,16 @@ func (mc *MultiClient) NonceAt(ctx context.Context, account common.Address, bloc return count, err } +func (mc *MultiClient) NonceAtHash(ctx context.Context, account common.Address, blockHash common.Hash) (uint64, error) { + var count uint64 + err := mc.retryWithBackups("NonceAtHash", func(client *ethclient.Client) error { + var err error + count, err = client.NonceAtHash(ctx, account, blockHash) + return err + }) + return count, err +} + func (mc *MultiClient) WaitMined(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) { mc.lggr.Debugf("Waiting for tx %s to be mined for chain %s", tx.Hash().Hex(), mc.chainName) // no retries here because we want to wait for the tx to be mined diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 18a44e30fa5..e90b37d09c7 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -2056,6 +2056,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -2161,6 +2164,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -2266,6 +2272,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -2371,6 +2380,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -2477,6 +2489,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -2586,6 +2601,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -2691,6 +2709,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -2797,6 +2818,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -2902,6 +2926,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3006,6 +3033,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3110,6 +3140,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3215,6 +3248,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3321,6 +3357,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3426,6 +3465,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3531,6 +3573,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3635,6 +3680,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3741,6 +3789,9 @@ ResendAfterThreshold = '3m0s' Enabled = true MinAttempts = 3 +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3847,6 +3898,9 @@ ResendAfterThreshold = '3m0s' Enabled = true MinAttempts = 3 +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -3952,6 +4006,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4061,6 +4118,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4166,6 +4226,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4275,6 +4338,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4383,6 +4449,9 @@ ResendAfterThreshold = '2m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4488,6 +4557,9 @@ ResendAfterThreshold = '2m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4593,6 +4665,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4701,6 +4776,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4810,6 +4888,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -4919,6 +5000,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5028,6 +5112,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5132,6 +5219,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5237,6 +5327,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5343,6 +5436,9 @@ ResendAfterThreshold = '3m0s' Enabled = true MinAttempts = 3 +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5448,6 +5544,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5553,6 +5652,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5658,6 +5760,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5767,6 +5872,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5875,6 +5983,9 @@ ResendAfterThreshold = '0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -5981,6 +6092,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6091,6 +6205,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6200,6 +6317,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6305,6 +6425,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6410,6 +6533,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6520,6 +6646,9 @@ ResendAfterThreshold = '3m0s' Enabled = true MinAttempts = 3 +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6624,6 +6753,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6729,6 +6861,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6834,6 +6969,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -6943,6 +7081,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7053,6 +7194,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7162,6 +7306,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7267,6 +7414,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7376,6 +7526,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7482,6 +7635,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7591,6 +7747,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7700,6 +7859,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7808,6 +7970,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -7913,6 +8078,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8018,6 +8186,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8123,6 +8294,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8231,6 +8405,9 @@ Enabled = true Threshold = 90 MinAttempts = 3 +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8343,6 +8520,9 @@ Enabled = true Threshold = 90 MinAttempts = 3 +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8451,6 +8631,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8556,6 +8739,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8664,6 +8850,9 @@ ResendAfterThreshold = '3m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8770,6 +8959,9 @@ Enabled = true Threshold = 50 MinAttempts = 3 +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8876,6 +9068,9 @@ Enabled = true Threshold = 50 MinAttempts = 3 +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -8981,6 +9176,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9086,6 +9284,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9195,6 +9396,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9299,6 +9503,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9403,6 +9610,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9508,6 +9718,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9617,6 +9830,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9727,6 +9943,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9836,6 +10055,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -9944,6 +10166,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -10053,6 +10278,9 @@ ResendAfterThreshold = '1m0s' Enabled = true DetectionApiUrl = 'https://sepolia-venus.scroll.io' +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -10163,6 +10391,9 @@ ResendAfterThreshold = '1m0s' Enabled = true DetectionApiUrl = 'https://venus.scroll.io' +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -10272,6 +10503,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -10381,6 +10615,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -10490,6 +10727,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -10595,6 +10835,9 @@ ResendAfterThreshold = '30s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -10704,6 +10947,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -10809,6 +11055,9 @@ ResendAfterThreshold = '1m0s' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -11180,6 +11429,40 @@ MinAttempts = 3 # Example ``` MinAttempts configures the minimum number of broadcasted attempts a transaction has to have before it is evaluated further for being terminally stuck. This threshold is only applied if there is no custom API to identify stuck transactions provided by the chain. Ensure the gas estimator configs take more bump attempts before reaching the configured max gas price. +## EVM.Transactions.TransactionManagerV2 +```toml +[EVM.Transactions.TransactionManagerV2] +Enabled = false # Default +BlockTime = '10s' # Example +CustomURL = 'https://example.api.io' # Example +DualBroadcast = false # Example +``` + + +### Enabled +```toml +Enabled = false # Default +``` +Enabled enables TransactionManagerV2. + +### BlockTime +```toml +BlockTime = '10s' # Example +``` +BlockTime controls the frequency of the backfill loop of TransactionManagerV2. + +### CustomURL +```toml +CustomURL = 'https://example.api.io' # Example +``` +CustomURL configures the base url of a custom endpoint used by the ChainDualBroadcast chain type. + +### DualBroadcast +```toml +DualBroadcast = false # Example +``` +DualBroadcast enables DualBroadcast functionality. + ## EVM.BalanceMonitor ```toml [EVM.BalanceMonitor] diff --git a/go.mod b/go.mod index 791ee8490bc..e5054657f77 100644 --- a/go.mod +++ b/go.mod @@ -79,15 +79,15 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.34 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4 github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20250115135646-ac859d85e7e3 github.com/smartcontractkit/chainlink-feeds v0.1.1 github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616-a2ea5e50b260 github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 - github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 + github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 github.com/smartcontractkit/libocr v0.0.0-20241223215956-e5b78d8e3919 github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20241009055228-33d0c0bf38de diff --git a/go.sum b/go.sum index fa4f8b89591..61c0e80ecb7 100644 --- a/go.sum +++ b/go.sum @@ -1152,8 +1152,8 @@ github.com/smartcontractkit/chain-selectors v1.0.34 h1:MJ17OGu8+jjl426pcKrJkCf3f github.com/smartcontractkit/chain-selectors v1.0.34/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 h1:TeWnxdgSO2cYCKcBMwdkRcs/YdhSvXoWqqw7QWz/KeI= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103/go.mod h1:ncjd6mPZSRlelEqH/2KeLE1pU3UlqzBSn8RYkEoECzY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd h1:1xtdmHoleuTNxXlcRAZ7MslnKATwDDWOZfdcBuaN2SE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd/go.mod h1:JJZMCB75aVSAiPNW032F9WUKTlLztTd8bbQB5MEaZa4= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4 h1:cf7mgbR8OelUnq49x0vYLy1XWddw4t1Q1YsBPxUQY4M= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= @@ -1166,10 +1166,10 @@ github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616- github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616-a2ea5e50b260/go.mod h1:4JqpgFy01LaqG1yM2iFTzwX3ZgcAvW9WdstBZQgPHzU= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 h1:L6UDu0GzSKfz+/nMSz9yfYrgOpW1/OXhiVK60PxVsnY= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3/go.mod h1:52U0UH8K0Qwu+HB1LMc+5V27ru2Tgy29YPT+2HkMevY= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 h1:kDW6Ab8vGRK2y+DPEvvhU2It8UCS9FK5ZQqIVjDfpK4= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5/go.mod h1:uHVnYLMgJ1rTcNoVxhBpy38t69gXq0j+LN3TkcIVE3U= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 h1:tQCjnIjY88AClWXApaTS+/ihQYM1GVCrbD9W00eh11E= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34/go.mod h1:lgG9JT2P19KnYuBheKIis5ZeCO+AaSta+RfzvwDQS2Y= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20241223215956-e5b78d8e3919 h1:IpGoPTXpvllN38kT2z2j13sifJMz4nbHglidvop7mfg= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 6204d19bd7d..3aeb534b4a6 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -46,7 +46,7 @@ require ( github.com/slack-go/slack v0.15.0 github.com/smartcontractkit/chain-selectors v1.0.36 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4 github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 @@ -434,8 +434,8 @@ require ( github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616-a2ea5e50b260 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 // indirect github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.2-0.20250110073248-456673e8eea2 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 452f3e71633..89ec6e6356e 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1410,8 +1410,8 @@ github.com/smartcontractkit/chain-selectors v1.0.36 h1:KSpO8I+JOiuyN4FuXsV471sPo github.com/smartcontractkit/chain-selectors v1.0.36/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 h1:TeWnxdgSO2cYCKcBMwdkRcs/YdhSvXoWqqw7QWz/KeI= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103/go.mod h1:ncjd6mPZSRlelEqH/2KeLE1pU3UlqzBSn8RYkEoECzY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd h1:1xtdmHoleuTNxXlcRAZ7MslnKATwDDWOZfdcBuaN2SE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd/go.mod h1:JJZMCB75aVSAiPNW032F9WUKTlLztTd8bbQB5MEaZa4= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250103152858-8973fd0c912b h1:UBXi9Yj8YSMHDDaxQLu273x1fWjyEL9xP58nuJsqZfg= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250103152858-8973fd0c912b/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4 h1:cf7mgbR8OelUnq49x0vYLy1XWddw4t1Q1YsBPxUQY4M= @@ -1428,10 +1428,10 @@ github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 h1:0ewLMbAz3 github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 h1:L6UDu0GzSKfz+/nMSz9yfYrgOpW1/OXhiVK60PxVsnY= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3/go.mod h1:52U0UH8K0Qwu+HB1LMc+5V27ru2Tgy29YPT+2HkMevY= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 h1:kDW6Ab8vGRK2y+DPEvvhU2It8UCS9FK5ZQqIVjDfpK4= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5/go.mod h1:uHVnYLMgJ1rTcNoVxhBpy38t69gXq0j+LN3TkcIVE3U= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 h1:tQCjnIjY88AClWXApaTS+/ihQYM1GVCrbD9W00eh11E= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34/go.mod h1:lgG9JT2P19KnYuBheKIis5ZeCO+AaSta+RfzvwDQS2Y= github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.2-0.20250110073248-456673e8eea2 h1:nTUoe7GZLw17nPLV5t3Vgf4U4pf+VW0Uko5xpNiKdKU= github.com/smartcontractkit/chainlink-testing-framework/framework v0.4.2-0.20250110073248-456673e8eea2/go.mod h1:mMUqvS3BZfvN1OfK4OFTYf1+T0X6nwmSXJM2keaPsSM= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 h1:GDGrC5OGiV0RyM1znYWehSQXyZQWTOzrEeJRYmysPCE= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index a8f65775a79..52db6f8b3dc 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -411,14 +411,14 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.36 // indirect github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20250115135646-ac859d85e7e3 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616-a2ea5e50b260 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 // indirect - github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 // indirect + github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 // indirect github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 // indirect github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 00a7dbcbcfc..003e4b0124c 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1397,8 +1397,8 @@ github.com/smartcontractkit/chain-selectors v1.0.36 h1:KSpO8I+JOiuyN4FuXsV471sPo github.com/smartcontractkit/chain-selectors v1.0.36/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103 h1:TeWnxdgSO2cYCKcBMwdkRcs/YdhSvXoWqqw7QWz/KeI= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250110181647-9dba278f2103/go.mod h1:ncjd6mPZSRlelEqH/2KeLE1pU3UlqzBSn8RYkEoECzY= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd h1:1xtdmHoleuTNxXlcRAZ7MslnKATwDDWOZfdcBuaN2SE= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250117200850-6cf7498adbfd/go.mod h1:JJZMCB75aVSAiPNW032F9WUKTlLztTd8bbQB5MEaZa4= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4 h1:cf7mgbR8OelUnq49x0vYLy1XWddw4t1Q1YsBPxUQY4M= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250117101554-1922eef0bdd4/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= @@ -1411,10 +1411,10 @@ github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616- github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250115203616-a2ea5e50b260/go.mod h1:4JqpgFy01LaqG1yM2iFTzwX3ZgcAvW9WdstBZQgPHzU= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3 h1:L6UDu0GzSKfz+/nMSz9yfYrgOpW1/OXhiVK60PxVsnY= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250110142550-e2a9566d39f3/go.mod h1:52U0UH8K0Qwu+HB1LMc+5V27ru2Tgy29YPT+2HkMevY= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8 h1:tNS7U9lrxkFvEuyxQv11HHOiV9LPDGC9wYEy+yM/Jv4= -github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241202202529-2033490e77b8/go.mod h1:EBrEgcdIbwepqguClkv8Ohy7CbyWSJaE4EC9aBJlQK0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5 h1:kDW6Ab8vGRK2y+DPEvvhU2It8UCS9FK5ZQqIVjDfpK4= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20250117143722-fb6416c087a5/go.mod h1:uHVnYLMgJ1rTcNoVxhBpy38t69gXq0j+LN3TkcIVE3U= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34 h1:tQCjnIjY88AClWXApaTS+/ihQYM1GVCrbD9W00eh11E= +github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20250117171710-b6481e9fcb34/go.mod h1:lgG9JT2P19KnYuBheKIis5ZeCO+AaSta+RfzvwDQS2Y= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 h1:GDGrC5OGiV0RyM1znYWehSQXyZQWTOzrEeJRYmysPCE= github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2/go.mod h1:DsT43c1oTBmp3iQkMcoZOoKThwZvt8X3Pz6UmznJ4GY= github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19 h1:9PMwKNqFKc5FXf4VchyD3CGzZelnSgi13fgVdT2X7T4= diff --git a/integration-tests/smoke/ccip/ccip_add_chain_test.go b/integration-tests/smoke/ccip/ccip_add_chain_test.go new file mode 100644 index 00000000000..3e7a8887e46 --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_add_chain_test.go @@ -0,0 +1,813 @@ +package ccip + +import ( + "math/big" + "slices" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" +) + +func Test_AddChain(t *testing.T) { + const ( + numChains = 4 + usersPerChain = 2 + ) + + // Set up an env with 4 chains but initially + // only deploy and configure 3 of them. + e, tEnv := testhelpers.NewMemoryEnvironment( + t, + testhelpers.WithNumOfChains(numChains), + testhelpers.WithNumOfNodes(4), + testhelpers.WithPrerequisiteDeploymentOnly(nil), + testhelpers.WithNumOfUsersPerChain(usersPerChain), + testhelpers.WithNoJobsAndContracts(), + testhelpers.WithOCRConfigOverride(func(params *ccipcs.CCIPOCRParams) { + // Only 1 boost (=OCR round) is enough to cover the fee + params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 + }), + ) + + allChains := maps.Keys(e.Env.Chains) + slices.Sort(allChains) + toDeploy := e.Env.AllChainSelectorsExcluding([]uint64{allChains[0]}) + require.Len(t, toDeploy, numChains-1) + remainingChain := allChains[0] + t.Log("initially deploying chains:", toDeploy, "and afterwards adding chain", remainingChain) + + ///////////////////////////////////// + // START Setup initial chains + ///////////////////////////////////// + e = setupChain( + t, + e, + tEnv, + toDeploy, + true, // deployJobs + true, // deployHomeChain + false, // mcmsEnabled + ) + + state, err := ccipcs.LoadOnchainState(e.Env) + require.NoError(t, err) + + // check RMNRemote is up and RMNProxy is correctly wired. + assertRMNRemoteAndProxyState(t, toDeploy, state) + + // Setup densely connected lanes between all chains. + for _, source := range toDeploy { + for _, dest := range toDeploy { + if source == dest { + continue + } + testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig( + t, + &e, + state, + source, + dest, + false, // isTestRouter + ) + } + } + + // Transfer ownership of all contracts to the MCMS and renounce the timelock deployer. + transferToMCMSAndRenounceTimelockDeployer( + t, + e, + toDeploy, + state, + false, // onlyChainContracts + ) + + // At this stage we can send some requests and confirm the setup is working. + sendMsgs := func( + sources []uint64, + dests []uint64, + testRouter bool, + ) (gasPricePreUpdate map[testhelpers.SourceDestPair]*big.Int, startBlocks map[uint64]*uint64) { + startBlocks = make(map[uint64]*uint64) + gasPricePreUpdate = make(map[testhelpers.SourceDestPair]*big.Int) + var ( + expectedSeqNum = make(map[testhelpers.SourceDestPair]uint64) + expectedSeqNumExec = make(map[testhelpers.SourceDestPair][]uint64) + ) + for _, source := range sources { + for _, dest := range dests { + if source == dest { + continue + } + + gp, err := state.Chains[source].FeeQuoter.GetDestinationChainGasPrice(&bind.CallOpts{ + Context: tests.Context(t), + }, dest) + require.NoError(t, err) + gasPricePreUpdate[testhelpers.SourceDestPair{ + SourceChainSelector: source, + DestChainSelector: dest, + }] = gp.Value + + latesthdr, err := e.Env.Chains[dest].Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + msgSentEvent := testhelpers.TestSendRequest(t, e.Env, state, source, dest, testRouter, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + + startBlocks[dest] = &block + expectedSeqNum[testhelpers.SourceDestPair{ + SourceChainSelector: source, + DestChainSelector: dest, + }] = msgSentEvent.SequenceNumber + expectedSeqNumExec[testhelpers.SourceDestPair{ + SourceChainSelector: source, + DestChainSelector: dest, + }] = append(expectedSeqNumExec[testhelpers.SourceDestPair{ + SourceChainSelector: source, + DestChainSelector: dest, + }], msgSentEvent.SequenceNumber) + } + } + + // Confirm execution of the message + testhelpers.ConfirmCommitForAllWithExpectedSeqNums(t, e.Env, state, expectedSeqNum, startBlocks) + testhelpers.ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) + return gasPricePreUpdate, startBlocks + } + + sendMsgs(toDeploy, toDeploy, false) + + ///////////////////////////////////// + // END Setup initial chains + ///////////////////////////////////// + + // TODO: Not working. Need to fix/figure out why. + // gasPricePreUpdate, startBlocks := sendMsgs(toDeploy) + // for sourceDestPair, preUpdateGp := range gasPricePreUpdate { + // // check that each chain's fee quoter has updated its gas price + // // for all dests. + // err := ConfirmGasPriceUpdated( + // t, + // e.Env.Chains[sourceDestPair.DestChainSelector], + // state.Chains[sourceDestPair.SourceChainSelector].FeeQuoter, + // *startBlocks[sourceDestPair.DestChainSelector], + // preUpdateGp, + // ) + // require.NoError(t, err) + // } + + ///////////////////////////////////// + // START Deploy to the remaining chain. + ///////////////////////////////////// + + // MCMS needs to be enabled because the home chain contracts have been + // transferred to MCMS. + e = setupChain( + t, + e, + tEnv, + []uint64{remainingChain}, + false, // deployJobs + false, // deployHomeChain + true, // mcmsEnabled + ) + + state, err = ccipcs.LoadOnchainState(e.Env) + require.NoError(t, err) + + assertRMNRemoteAndProxyState(t, []uint64{remainingChain}, state) + + // TODO: wait for gas price of new chain to be updated on all other chains. + + ///////////////////////////////////// + // END Deploy to the remaining chain. + ///////////////////////////////////// + + ///////////////////////////////////// + // START Wire up toDeploy -> remainingChain outbound. + ///////////////////////////////////// + e = setupOutboundWiring( + t, + e, + toDeploy, + []uint64{remainingChain}, + true, // testRouterEnabled + true, // mcmsEnabled + ) + + state, err = ccipcs.LoadOnchainState(e.Env) + require.NoError(t, err) + + assertChainWiringOutbound( + t, + state, + remainingChain, + toDeploy, + true, // testRouterEnabled + ) + + // At this point we can send messages from the test router to the new chain. + // These won't be processed yet because the offRamp is not aware of these new sources. + + ///////////////////////////////////// + // END Wire up toDeploy -> remainingChain outbound. + ///////////////////////////////////// + + ///////////////////////////////////// + // START Wire up toDeploy -> remainingChain inbound on remainingChain. + ///////////////////////////////////// + + // UpdateOffRampSourcesConfig called on the new chain to enable existing sources. Also with the test router. + // UpdateRouterRampsConfig to enable the existing sources on the new test router. + // This means we can send messages from toDeploy to remainingChain. + // NOTE: not using MCMS since haven't transferred to timelock yet. + e = setupInboundWiring(t, e, toDeploy, []uint64{remainingChain}, true, false) + + assertChainWiringInbound( + t, + state, + remainingChain, + toDeploy, + true, // testRouterEnabled + ) + + // Send messages from toDeploy to the newly added chain thru the test router. + sendMsgs(toDeploy, []uint64{remainingChain}, true) + + ///////////////////////////////////// + // END Wire up toDeploy -> remainingChain inbound on remainingChain. + ///////////////////////////////////// + + ///////////////////////////////////// + // START Wire up remainingChain -> toDeploy outbound. + ///////////////////////////////////// + + // Now we switch to testing outbound from the new chain. + // This amounts to enabling a new lane on the existing OCR instances + // (assuming by default we want to enable all chains). + e = setupOutboundWiring( + t, + e, + []uint64{remainingChain}, + toDeploy, + true, // testRouterEnabled + false, // mcmsEnabled + ) + + // sanity check that everything is correctly set up. + for _, chain := range toDeploy { + assertChainWiringOutbound(t, state, chain, []uint64{remainingChain}, true) + } + + ///////////////////////////////////// + // END Wire up remainingChain -> toDeploy outbound. + ///////////////////////////////////// + + ///////////////////////////////////// + // START Wire up remainingChain -> toDeploy inbound on toDeploy. + ///////////////////////////////////// + + e = setupInboundWiring(t, e, []uint64{remainingChain}, toDeploy, true, true) + + for _, chain := range toDeploy { + assertChainWiringInbound(t, state, chain, []uint64{remainingChain}, true) + } + + ///////////////////////////////////// + // END Wire up remainingChain -> toDeploy inbound on toDeploy. + ///////////////////////////////////// + + ///////////////////////////////////// + // START send messages from remainingChain to toDeploy. + ///////////////////////////////////// + + // Send messages from remainingChain to toDeploy thru the test router. + sendMsgs([]uint64{remainingChain}, toDeploy, true) + + ///////////////////////////////////// + // END send messages from remainingChain to toDeploy. + ///////////////////////////////////// + + // Transfer the new chain contracts to the timelock ownership. + transferToMCMSAndRenounceTimelockDeployer( + t, + e, + []uint64{remainingChain}, + state, + true, // onlyChainContracts + ) + + // Once verified with the test routers, the last step is to whitelist the new chain on the real routers everywhere with + // UpdateRouterRamps, UpdateOffRampSources and UpdateOnRampDests with TestRouter=False. + // This is basically the same as the wiring done above, just setting testRouterEnabled=false. + + ///////////////////////////////////// + // START Wire up toDeploy -> remainingChain outbound through real router. + ///////////////////////////////////// + e = setupOutboundWiring( + t, + e, + toDeploy, + []uint64{remainingChain}, + false, // testRouterEnabled + true, // mcmsEnabled + ) + + state, err = ccipcs.LoadOnchainState(e.Env) + require.NoError(t, err) + + assertChainWiringOutbound( + t, + state, + remainingChain, + toDeploy, + false, // testRouterEnabled + ) + ///////////////////////////////////// + // END Wire up toDeploy -> remainingChain outbound through real router. + ///////////////////////////////////// + + ///////////////////////////////////// + // START Wire up toDeploy -> remainingChain inbound on remainingChain through real router. + ///////////////////////////////////// + + e = setupInboundWiring( + t, + e, + toDeploy, + []uint64{remainingChain}, + false, // testRouterEnabled + true, // mcmsEnabled + ) + + assertChainWiringInbound( + t, + state, + remainingChain, + toDeploy, + false, // testRouterEnabled + ) + + ///////////////////////////////////// + // END Wire up toDeploy -> remainingChain inbound on remainingChain through real router. + ///////////////////////////////////// + + ///////////////////////////////////// + // START Wire up remainingChain -> toDeploy outbound through real router. + ///////////////////////////////////// + + e = setupOutboundWiring( + t, + e, + []uint64{remainingChain}, + toDeploy, + false, // testRouterEnabled + true, // mcmsEnabled + ) + + // sanity check that everything is correctly set up. + for _, chain := range toDeploy { + assertChainWiringOutbound( + t, + state, + chain, + []uint64{remainingChain}, + false, // testRouterEnabled + ) + } + + ///////////////////////////////////// + // END Wire up remainingChain -> toDeploy outbound through real router. + ///////////////////////////////////// + + ///////////////////////////////////// + // START Wire up remainingChain -> toDeploy inbound on toDeploy through real router. + ///////////////////////////////////// + + e = setupInboundWiring( + t, + e, + []uint64{remainingChain}, + toDeploy, + false, // testRouterEnabled + true, // mcmsEnabled + ) + + for _, chain := range toDeploy { + assertChainWiringInbound( + t, + state, + chain, + []uint64{remainingChain}, + false, // testRouterEnabled + ) + } + + ///////////////////////////////////// + // END Wire up remainingChain -> toDeploy inbound on toDeploy. + ///////////////////////////////////// + + // Send messages from toDeploy to the newly added chain thru the real router. + sendMsgs(toDeploy, []uint64{remainingChain}, false) +} + +// setupInboundWiring sets up the newChain to be able to receive ccip messages from the provided sources. +// This only touches the newChain and does not touch the sources. +func setupInboundWiring( + t *testing.T, + e testhelpers.DeployedEnv, + sources []uint64, + newChains []uint64, + testRouterEnabled, + mcmsEnabled bool, +) testhelpers.DeployedEnv { + var mcmsConfig *ccipcs.MCMSConfig + if mcmsEnabled { + mcmsConfig = &ccipcs.MCMSConfig{ + MinDelay: 0, + } + } + + var err error + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, e.TimelockContracts(t), []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(ccipcs.UpdateOffRampSourcesChangeset), + Config: ccipcs.UpdateOffRampSourcesConfig{ + UpdatesByChain: offRampSourceUpdates(t, newChains, sources, testRouterEnabled), + MCMS: mcmsConfig, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(ccipcs.UpdateRouterRampsChangeset), + Config: ccipcs.UpdateRouterRampsConfig{ + TestRouter: testRouterEnabled, + UpdatesByChain: routerOffRampUpdates(t, newChains, sources), + MCMS: mcmsConfig, + }, + }, + }) + require.NoError(t, err) + + return e +} + +// setupOutboundWiring sets up the given sources to be able to request ccip messages to the newChain. +// This only touches the sources and does not touch newChain. +// Therefore any requests to newChain will not be processed until the inbound wiring is set up. +func setupOutboundWiring( + t *testing.T, + e testhelpers.DeployedEnv, + sources []uint64, + newChains []uint64, + testRouterEnabled, + mcmsEnabled bool, +) testhelpers.DeployedEnv { + var mcmsConfig *ccipcs.MCMSConfig + if mcmsEnabled { + mcmsConfig = &ccipcs.MCMSConfig{ + MinDelay: 0, + } + } + + var err error + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, e.TimelockContracts(t), []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(ccipcs.UpdateOnRampsDestsChangeset), + Config: ccipcs.UpdateOnRampDestsConfig{ + UpdatesByChain: onRampDestUpdates(t, newChains, sources, testRouterEnabled), + MCMS: mcmsConfig, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(ccipcs.UpdateFeeQuoterPricesChangeset), + Config: ccipcs.UpdateFeeQuoterPricesConfig{ + PricesByChain: feeQuoterPricesByChain(t, newChains, sources), + MCMS: mcmsConfig, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(ccipcs.UpdateFeeQuoterDestsChangeset), + Config: ccipcs.UpdateFeeQuoterDestsConfig{ + UpdatesByChain: feeQuoterDestUpdates(t, newChains, sources), + MCMS: mcmsConfig, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(ccipcs.UpdateRouterRampsChangeset), + Config: ccipcs.UpdateRouterRampsConfig{ + TestRouter: testRouterEnabled, + UpdatesByChain: routerOnRampUpdates(t, newChains, sources), + MCMS: mcmsConfig, + }, + }, + }) + require.NoError(t, err) + + return e +} + +// setupChain will deploy the ccip chain contracts to the provided chains. +// Based on the flags provided, it will also deploy the jobs and home chain contracts. +// mcmsEnabled should be set to true if the home chain contracts have been transferred to MCMS. +func setupChain(t *testing.T, e testhelpers.DeployedEnv, tEnv testhelpers.TestEnvironment, chains []uint64, deployJobs, deployHomeChain, mcmsEnabled bool) testhelpers.DeployedEnv { + e = testhelpers.AddCCIPContractsToEnvironment( + t, + chains, + tEnv, + deployJobs, + deployHomeChain, + mcmsEnabled, + ) + + // Need to update what the RMNProxy is pointing to, otherwise plugin will not work. + var err error + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, e.TimelockContracts(t), []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(ccipcs.SetRMNRemoteOnRMNProxyChangeset), + Config: ccipcs.SetRMNRemoteOnRMNProxyConfig{ + ChainSelectors: chains, + }, + }, + }) + require.NoError(t, err) + + return e +} + +// assertChainWiringInbound checks that the newChain has the existingChains enabled as sources. +// It only checks the inbound wiring on the newChain. +// It doesn't check that the existingChains have the newChain enabled as a dest. +func assertChainWiringInbound( + t *testing.T, + state ccipcs.CCIPOnChainState, + newChain uint64, + existingChains []uint64, + testRouterEnabled bool, +) { + for _, existingChain := range existingChains { + var rtr *router.Router + if testRouterEnabled { + rtr = state.Chains[newChain].TestRouter + } else { + rtr = state.Chains[newChain].Router + } + + // check that the offRamp has the existing chain enabled as a source. + // in addition, check that the onRamp set in the source chain config + // matches the address of the onRamp on the existing chain. + dcc, err := state.Chains[newChain].OffRamp.GetSourceChainConfig(&bind.CallOpts{ + Context: tests.Context(t), + }, existingChain) + require.NoError(t, err) + require.Equal(t, rtr.Address(), dcc.Router) + require.Equal(t, dcc.OnRamp, common.LeftPadBytes(state.Chains[existingChain].OnRamp.Address().Bytes(), 32)) + + // check that the router has the existing chain enabled as a source. + routerOffRamps, err := rtr.GetOffRamps(&bind.CallOpts{ + Context: tests.Context(t), + }) + require.NoError(t, err) + + var found bool + for _, offRamp := range routerOffRamps { + if offRamp.SourceChainSelector == existingChain { + require.Equal(t, state.Chains[newChain].OffRamp.Address(), offRamp.OffRamp) + found = true + break + } + } + require.True(t, found) + } +} + +// assertChainWiringOutbound checks that newChain can be requested from existingChains. +// This only checks the outbound wiring on existingChains. +// It doesn't check that the newChain can process the requests. +func assertChainWiringOutbound( + t *testing.T, + state ccipcs.CCIPOnChainState, + newChain uint64, + existingChains []uint64, + testRouterEnabled bool, +) { + for _, existingChain := range existingChains { + var rtr *router.Router + if testRouterEnabled { + rtr = state.Chains[existingChain].TestRouter + } else { + rtr = state.Chains[existingChain].Router + } + + // check that the onRamp has the new chain enabled as a dest. + dcc, err := state.Chains[existingChain].OnRamp.GetDestChainConfig(&bind.CallOpts{ + Context: tests.Context(t), + }, newChain) + require.NoError(t, err) + require.Equal(t, rtr.Address(), dcc.Router) + + // check that the feeQuoter has the new chain enabled as a dest. + fqdcc, err := state.Chains[existingChain].FeeQuoter.GetDestChainConfig(&bind.CallOpts{ + Context: tests.Context(t), + }, newChain) + require.NoError(t, err) + require.True(t, fqdcc.IsEnabled) + + // check that the router has the new chain enabled as a dest. + routerOnRamp, err := rtr.GetOnRamp(&bind.CallOpts{ + Context: tests.Context(t), + }, newChain) + require.NoError(t, err) + require.Equal(t, state.Chains[existingChain].OnRamp.Address(), routerOnRamp) + } +} + +// routerOffRampUpdates adds the provided sources to the router of the provided dest chain. +func routerOffRampUpdates(t *testing.T, dests []uint64, sources []uint64) (updates map[uint64]ccipcs.RouterUpdates) { + updates = make(map[uint64]ccipcs.RouterUpdates) + for _, source := range sources { + for _, dest := range dests { + require.NotEqual(t, source, dest) + if _, ok := updates[dest]; !ok { + updates[dest] = ccipcs.RouterUpdates{ + OffRampUpdates: map[uint64]bool{ + source: true, + }, + } + } else { + updates[dest].OffRampUpdates[source] = true + } + } + } + return +} + +// routerOnRampUpdates sets each dest selector in the given dest chains slice on the router +// to point to the local onramp on each source chain. +func routerOnRampUpdates(t *testing.T, dests []uint64, sources []uint64) (updates map[uint64]ccipcs.RouterUpdates) { + updates = make(map[uint64]ccipcs.RouterUpdates) + for _, source := range sources { + for _, dest := range dests { + require.NotEqual(t, source, dest) + if _, ok := updates[source]; !ok { + updates[source] = ccipcs.RouterUpdates{ + OnRampUpdates: map[uint64]bool{ + dest: true, + }, + } + } else { + updates[source].OnRampUpdates[dest] = true + } + } + } + return +} + +// feeQuoterDestUpdates adds a fee quoter configuration for the provided dest chains on the fee quoters on the provided sources. +func feeQuoterDestUpdates(t *testing.T, dests []uint64, sources []uint64) (updates map[uint64]map[uint64]fee_quoter.FeeQuoterDestChainConfig) { + updates = make(map[uint64]map[uint64]fee_quoter.FeeQuoterDestChainConfig) + for _, source := range sources { + for _, dest := range dests { + require.NotEqual(t, source, dest) + if _, ok := updates[source]; !ok { + updates[source] = make(map[uint64]fee_quoter.FeeQuoterDestChainConfig) + } + updates[source][dest] = ccipcs.DefaultFeeQuoterDestChainConfig() + } + } + return +} + +// feeQuoterPricesByChain sets the gas price for the provided dests on the fee quoters in the provided sources. +func feeQuoterPricesByChain(t *testing.T, dests []uint64, sources []uint64) (prices map[uint64]ccipcs.FeeQuoterPriceUpdatePerSource) { + prices = make(map[uint64]ccipcs.FeeQuoterPriceUpdatePerSource) + for _, source := range sources { + prices[source] = ccipcs.FeeQuoterPriceUpdatePerSource{ + GasPrices: make(map[uint64]*big.Int), + } + for _, dest := range dests { + require.NotEqual(t, source, dest) + prices[source].GasPrices[dest] = testhelpers.DefaultGasPrice + } + } + return +} + +// onRampDestUpdates adds the provided dests as destination chains to the onRamps on the provided sources. +func onRampDestUpdates(t *testing.T, dests []uint64, sources []uint64, testRouterEnabled bool) (updates map[uint64]map[uint64]ccipcs.OnRampDestinationUpdate) { + updates = make(map[uint64]map[uint64]ccipcs.OnRampDestinationUpdate) + for _, source := range sources { + for _, dest := range dests { + require.NotEqual(t, source, dest) + if _, ok := updates[source]; !ok { + updates[source] = map[uint64]ccipcs.OnRampDestinationUpdate{ + dest: { + IsEnabled: true, + TestRouter: testRouterEnabled, + }, + } + } else { + updates[source][dest] = ccipcs.OnRampDestinationUpdate{ + IsEnabled: true, + TestRouter: testRouterEnabled, + } + } + } + } + return +} + +// offRampSourceUpdates adds the provided sources to the offRamp on the provided dest chains. +func offRampSourceUpdates(t *testing.T, dests []uint64, sources []uint64, testRouterEnabled bool) (updates map[uint64]map[uint64]ccipcs.OffRampSourceUpdate) { + updates = make(map[uint64]map[uint64]ccipcs.OffRampSourceUpdate) + for _, source := range sources { + for _, dest := range dests { + require.NotEqual(t, source, dest) + if _, ok := updates[dest]; !ok { + updates[dest] = make(map[uint64]ccipcs.OffRampSourceUpdate) + } + updates[dest][source] = ccipcs.OffRampSourceUpdate{ + IsEnabled: true, + TestRouter: testRouterEnabled, + } + } + } + return +} + +func assertRMNRemoteAndProxyState(t *testing.T, chains []uint64, state ccipcs.CCIPOnChainState) { + for _, chain := range chains { + require.NotEqual(t, common.Address{}, state.Chains[chain].RMNRemote.Address()) + _, err := state.Chains[chain].RMNRemote.GetCursedSubjects(&bind.CallOpts{ + Context: tests.Context(t), + }) + require.NoError(t, err) + + // check which address RMNProxy is pointing to + rmnAddress, err := state.Chains[chain].RMNProxy.GetARM(&bind.CallOpts{ + Context: tests.Context(t), + }) + require.NoError(t, err) + require.Equal(t, state.Chains[chain].RMNRemote.Address(), rmnAddress) + + t.Log("RMNRemote address for chain", chain, "is:", state.Chains[chain].RMNRemote.Address().Hex()) + t.Log("RMNProxy address for chain", chain, "is:", state.Chains[chain].RMNProxy.Address().Hex()) + } +} + +func transferToMCMSAndRenounceTimelockDeployer( + t *testing.T, + e testhelpers.DeployedEnv, + chains []uint64, + state ccipcs.CCIPOnChainState, + onlyChainContracts bool, +) { + apps := make([]commonchangeset.ChangesetApplication, 0, len(chains)+1) + cfg := testhelpers.GenTestTransferOwnershipConfig(e, chains, state) + if onlyChainContracts { + // filter out the home chain contracts from e.HomeChainSel + var homeChainContracts = map[common.Address]struct{}{ + state.Chains[e.HomeChainSel].CapabilityRegistry.Address(): {}, + state.Chains[e.HomeChainSel].CCIPHome.Address(): {}, + state.Chains[e.HomeChainSel].RMNHome.Address(): {}, + } + var chainContracts []common.Address + for _, contract := range cfg.ContractsByChain[e.HomeChainSel] { + if _, ok := homeChainContracts[contract]; !ok { + chainContracts = append(chainContracts, contract) + } + } + cfg.ContractsByChain[e.HomeChainSel] = chainContracts + } + apps = append(apps, commonchangeset.ChangesetApplication{ + Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), + Config: cfg, + }) + for _, chain := range chains { + apps = append(apps, commonchangeset.ChangesetApplication{ + Changeset: commonchangeset.WrapChangeSet(commonchangeset.RenounceTimelockDeployer), + Config: commonchangeset.RenounceTimelockDeployerConfig{ + ChainSel: chain, + }, + }) + } + var err error + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, e.TimelockContracts(t), apps) + require.NoError(t, err) +} diff --git a/integration-tests/smoke/ccip/ccip_batching_test.go b/integration-tests/smoke/ccip/ccip_batching_test.go index 38b068dad92..7b5a9092cf1 100644 --- a/integration-tests/smoke/ccip/ccip_batching_test.go +++ b/integration-tests/smoke/ccip/ccip_batching_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "context" diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go index d1800174a25..61cd7830c85 100644 --- a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "context" diff --git a/integration-tests/smoke/ccip/ccip_fees_test.go b/integration-tests/smoke/ccip/ccip_fees_test.go index 7c329179e55..f5b74791bfa 100644 --- a/integration-tests/smoke/ccip/ccip_fees_test.go +++ b/integration-tests/smoke/ccip/ccip_fees_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "math/big" diff --git a/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go b/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go index 0035be6f71c..42fcacc58f3 100644 --- a/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go +++ b/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "math/big" diff --git a/integration-tests/smoke/ccip/ccip_message_limitations_test.go b/integration-tests/smoke/ccip/ccip_message_limitations_test.go index 01be93a01a1..ea775e5445d 100644 --- a/integration-tests/smoke/ccip/ccip_message_limitations_test.go +++ b/integration-tests/smoke/ccip/ccip_message_limitations_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "math/big" diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 3e28c7851ca..fc154b848be 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "context" diff --git a/integration-tests/smoke/ccip/ccip_migration_to_v_1_6_test.go b/integration-tests/smoke/ccip/ccip_migration_to_v_1_6_test.go index a0b62fe8182..d70a6e8a3fa 100644 --- a/integration-tests/smoke/ccip/ccip_migration_to_v_1_6_test.go +++ b/integration-tests/smoke/ccip/ccip_migration_to_v_1_6_test.go @@ -1,8 +1,9 @@ -package smoke +package ccip import ( "context" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -23,7 +24,6 @@ import ( ) func TestMigrateFromV1_5ToV1_6(t *testing.T) { - t.Skipf("CCIP-4868 -This test needs to be investigated for flakiness") // Deploy CCIP 1.5 with 3 chains and 4 nodes + 1 bootstrap // Deploy 1.5 contracts (excluding pools to start, but including MCMS) . e, _, tEnv := testsetups.NewIntegrationEnvironment( @@ -151,7 +151,7 @@ func TestMigrateFromV1_5ToV1_6(t *testing.T) { // add 1.6 contracts to the environment and send 1.6 jobs // First we need to deploy Homechain contracts and restart the nodes with updated cap registry // in this test we have already deployed home chain contracts and the nodes are already running with the deployed cap registry. - e = testhelpers.AddCCIPContractsToEnvironment(t, e.Env.AllChainSelectors(), tEnv) + e = testhelpers.AddCCIPContractsToEnvironment(t, e.Env.AllChainSelectors(), tEnv, true, true, false) // Set RMNProxy to point to RMNRemote. // nonce manager should point to 1.5 ramps e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, e.TimelockContracts(t), []commonchangeset.ChangesetApplication{ @@ -216,6 +216,7 @@ func TestMigrateFromV1_5ToV1_6(t *testing.T) { block := latesthdr.Number.Uint64() startBlocks[dest] = &block expectedSeqNumExec := make(map[testhelpers.SourceDestPair][]uint64) + expectedSeqNums := make(map[testhelpers.SourceDestPair]uint64) msgSentEvent, err := testhelpers.DoSendRequest( t, e.Env, state, testhelpers.WithSourceChain(src1), @@ -237,8 +238,18 @@ func TestMigrateFromV1_5ToV1_6(t *testing.T) { SourceChainSelector: src1, DestChainSelector: dest, }] = []uint64{msgSentEvent.SequenceNumber} + expectedSeqNums[testhelpers.SourceDestPair{ + SourceChainSelector: src1, + DestChainSelector: dest, + }] = msgSentEvent.SequenceNumber - // Wait for all exec reports to land + // This sleep is needed so that plugins come up and start indexing logs. + // Otherwise test will flake. + time.Sleep(15 * time.Second) + testhelpers.ReplayLogs(t, e.Env.Offchain, map[uint64]uint64{ + src1: msgSentEvent.Raw.BlockNumber, + }) + testhelpers.ConfirmCommitForAllWithExpectedSeqNums(t, e.Env, state, expectedSeqNums, startBlocks) testhelpers.ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) // send a message from real router, the send requested event should be received in 1.5 onRamp diff --git a/integration-tests/smoke/ccip/ccip_ooo_execution_test.go b/integration-tests/smoke/ccip/ccip_ooo_execution_test.go index 004dd3989f8..6a68ecd9e30 100644 --- a/integration-tests/smoke/ccip/ccip_ooo_execution_test.go +++ b/integration-tests/smoke/ccip/ccip_ooo_execution_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "fmt" diff --git a/integration-tests/smoke/ccip/ccip_reader_test.go b/integration-tests/smoke/ccip/ccip_reader_test.go index f907c06f58f..4345bd4d894 100644 --- a/integration-tests/smoke/ccip/ccip_reader_test.go +++ b/integration-tests/smoke/ccip/ccip_reader_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "context" @@ -19,6 +19,7 @@ import ( "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" "github.com/smartcontractkit/chainlink/v2/core/utils/testutils/heavyweight" "github.com/smartcontractkit/chainlink-ccip/plugintypes" @@ -290,6 +291,7 @@ func TestCCIPReader_GetOffRampConfigDigest(t *testing.T) { nil, chainD, addr.Bytes(), + ccipevm.NewExtraArgsCodec(), ) ccipReaderCommitDigest, err := reader.GetOffRampConfigDigest(ctx, consts.PluginTypeCommit) @@ -1409,7 +1411,8 @@ func testSetupRealContracts( contractReaders[chain] = cr } contractWriters := make(map[cciptypes.ChainSelector]types.ContractWriter) - reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(ctx, lggr, contractReaders, contractWriters, cciptypes.ChainSelector(destChain), nil) + edc := ccipevm.NewExtraArgsCodec() + reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(ctx, lggr, contractReaders, contractWriters, cciptypes.ChainSelector(destChain), nil, edc) return reader } @@ -1524,7 +1527,8 @@ func testSetup( contractReaders[chain] = cr } contractWriters := make(map[cciptypes.ChainSelector]types.ContractWriter) - reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(ctx, lggr, contractReaders, contractWriters, params.DestChain, nil) + edc := ccipevm.NewExtraArgsCodec() + reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(ctx, lggr, contractReaders, contractWriters, params.DestChain, nil, edc) t.Cleanup(func() { require.NoError(t, cr.Close()) diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index d8f5aae0882..9a33abc844d 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -1,10 +1,9 @@ -package smoke +package ccip import ( "context" "errors" "math/big" - "os" "slices" "strconv" "strings" @@ -239,7 +238,6 @@ const ( ) func runRmnTestCase(t *testing.T, tc rmnTestCase) { - require.NoError(t, os.Setenv("ENABLE_RMN", "true")) require.NoError(t, tc.validate()) ctx := testcontext.Get(t) diff --git a/integration-tests/smoke/ccip/ccip_token_price_updates_test.go b/integration-tests/smoke/ccip/ccip_token_price_updates_test.go index 4c15eecc545..8498417a6b6 100644 --- a/integration-tests/smoke/ccip/ccip_token_price_updates_test.go +++ b/integration-tests/smoke/ccip/ccip_token_price_updates_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "context" diff --git a/integration-tests/smoke/ccip/ccip_token_transfer_test.go b/integration-tests/smoke/ccip/ccip_token_transfer_test.go index ef9a4bea8b8..f612cba523f 100644 --- a/integration-tests/smoke/ccip/ccip_token_transfer_test.go +++ b/integration-tests/smoke/ccip/ccip_token_transfer_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "math/big" diff --git a/integration-tests/smoke/ccip/ccip_usdc_test.go b/integration-tests/smoke/ccip/ccip_usdc_test.go index b64f8056c13..acbca534b8f 100644 --- a/integration-tests/smoke/ccip/ccip_usdc_test.go +++ b/integration-tests/smoke/ccip/ccip_usdc_test.go @@ -1,4 +1,4 @@ -package smoke +package ccip import ( "math/big" diff --git a/testdata/scripts/health/multi-chain-loopp.txtar b/testdata/scripts/health/multi-chain-loopp.txtar new file mode 100644 index 00000000000..6eaa9d9e26e --- /dev/null +++ b/testdata/scripts/health/multi-chain-loopp.txtar @@ -0,0 +1,498 @@ +env CL_SOLANA_CMD=chainlink-solana +env CL_STARKNET_CMD=chainlink-starknet + +# start node +exec sh -c 'eval "echo \"$(cat config.toml.tmpl)\" > config.toml"' +exec chainlink node -c config.toml start -p password -a creds & + +# initialize client +env NODEURL=http://localhost:$PORT +exec curl --retry 10 --retry-max-time 60 --retry-connrefused $NODEURL +exec chainlink --remote-node-url $NODEURL admin login -file creds --bypass-version-check + +exec chainlink --remote-node-url $NODEURL health +cmp stdout out.txt + +exec chainlink --remote-node-url $NODEURL health -json +cp stdout compact.json +exec jq . compact.json +cmp stdout out.json + +exec chainlink --remote-node-url $NODEURL health -failing +cmp stdout out-unhealthy.txt + +exec chainlink --remote-node-url $NODEURL health -f -json +cp stdout compact.json +exec jq . compact.json +cmp stdout out-unhealthy.json + +-- testdb.txt -- +CL_DATABASE_URL +-- testport.txt -- +PORT + +-- password -- +T.tLHkcmwePT/p,]sYuntjwHKAsrhm#4eRs4LuKHwvHejWYAC2JP4M8HimwgmbaZ +-- creds -- +notreal@fakeemail.ch +fj293fbBnlQ!f9vNs + +-- config.toml.tmpl -- +[Webserver] +HTTPPort = $PORT + +[[Cosmos]] +ChainID = 'Foo' + +[[Cosmos.Nodes]] +Name = 'primary' +TendermintURL = 'http://tender.mint' + +[[EVM]] +ChainID = '1' + +[[EVM.Nodes]] +Name = 'fake' +WSURL = 'wss://foo.bar/ws' +HTTPURL = 'https://foo.bar' + +[[Solana]] +ChainID = 'Bar' + +[[Solana.Nodes]] +Name = 'primary' +URL = 'http://solana.web' + +[[Starknet]] +ChainID = 'Baz' + +[[Starknet.Nodes]] +Name = 'primary' +URL = 'http://stark.node' + +-- out.txt -- +ok Cosmos.Foo.Chain +ok Cosmos.Foo.Relayer +ok Cosmos.Foo.Txm +ok EVM.1 +ok EVM.1.BalanceMonitor +ok EVM.1.HeadBroadcaster +ok EVM.1.HeadTracker +! EVM.1.HeadTracker.HeadListener + Listener is not connected +ok EVM.1.LogBroadcaster +ok EVM.1.Relayer +ok EVM.1.Txm +ok EVM.1.Txm.BlockHistoryEstimator +ok EVM.1.Txm.Broadcaster +ok EVM.1.Txm.Confirmer +ok EVM.1.Txm.Finalizer +ok EVM.1.Txm.WrappedEvmEstimator +ok HeadReporter +ok Heartbeat +ok JobSpawner +ok Mailbox.Monitor +ok Mercury.WSRPCPool +ok Mercury.WSRPCPool.CacheSet +ok PipelineORM +ok PipelineRunner +ok PipelineRunner.BridgeCache +ok RetirementReportCache +ok Solana.Bar.RelayerService +ok Solana.Bar.RelayerService.PluginRelayerClient +ok Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana +ok Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Chain +ok Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Relayer +ok Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Txm +ok StarkNet.Baz.RelayerService +ok StarkNet.Baz.RelayerService.PluginRelayerClient +ok StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet +ok StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Chain +ok StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Relayer +ok StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Txm +ok TelemetryManager +ok WorkflowDBStore + +-- out-unhealthy.txt -- +! EVM.1.HeadTracker.HeadListener + Listener is not connected + +-- out.json -- +{ + "data": [ + { + "type": "checks", + "id": "Cosmos.Foo.Chain", + "attributes": { + "name": "Cosmos.Foo.Chain", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Cosmos.Foo.Relayer", + "attributes": { + "name": "Cosmos.Foo.Relayer", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Cosmos.Foo.Txm", + "attributes": { + "name": "Cosmos.Foo.Txm", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1", + "attributes": { + "name": "EVM.1", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.BalanceMonitor", + "attributes": { + "name": "EVM.1.BalanceMonitor", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.HeadBroadcaster", + "attributes": { + "name": "EVM.1.HeadBroadcaster", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.HeadTracker", + "attributes": { + "name": "EVM.1.HeadTracker", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.HeadTracker.HeadListener", + "attributes": { + "name": "EVM.1.HeadTracker.HeadListener", + "status": "failing", + "output": "Listener is not connected" + } + }, + { + "type": "checks", + "id": "EVM.1.LogBroadcaster", + "attributes": { + "name": "EVM.1.LogBroadcaster", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.Relayer", + "attributes": { + "name": "EVM.1.Relayer", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.Txm", + "attributes": { + "name": "EVM.1.Txm", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.Txm.BlockHistoryEstimator", + "attributes": { + "name": "EVM.1.Txm.BlockHistoryEstimator", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.Txm.Broadcaster", + "attributes": { + "name": "EVM.1.Txm.Broadcaster", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.Txm.Confirmer", + "attributes": { + "name": "EVM.1.Txm.Confirmer", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.Txm.Finalizer", + "attributes": { + "name": "EVM.1.Txm.Finalizer", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "EVM.1.Txm.WrappedEvmEstimator", + "attributes": { + "name": "EVM.1.Txm.WrappedEvmEstimator", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "HeadReporter", + "attributes": { + "name": "HeadReporter", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Heartbeat", + "attributes": { + "name": "Heartbeat", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "JobSpawner", + "attributes": { + "name": "JobSpawner", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Mailbox.Monitor", + "attributes": { + "name": "Mailbox.Monitor", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Mercury.WSRPCPool", + "attributes": { + "name": "Mercury.WSRPCPool", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Mercury.WSRPCPool.CacheSet", + "attributes": { + "name": "Mercury.WSRPCPool.CacheSet", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "PipelineORM", + "attributes": { + "name": "PipelineORM", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "PipelineRunner", + "attributes": { + "name": "PipelineRunner", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "PipelineRunner.BridgeCache", + "attributes": { + "name": "PipelineRunner.BridgeCache", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "RetirementReportCache", + "attributes": { + "name": "RetirementReportCache", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Solana.Bar.RelayerService", + "attributes": { + "name": "Solana.Bar.RelayerService", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Solana.Bar.RelayerService.PluginRelayerClient", + "attributes": { + "name": "Solana.Bar.RelayerService.PluginRelayerClient", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana", + "attributes": { + "name": "Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Chain", + "attributes": { + "name": "Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Chain", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Relayer", + "attributes": { + "name": "Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Relayer", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Txm", + "attributes": { + "name": "Solana.Bar.RelayerService.PluginRelayerClient.PluginSolana.Txm", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "StarkNet.Baz.RelayerService", + "attributes": { + "name": "StarkNet.Baz.RelayerService", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "StarkNet.Baz.RelayerService.PluginRelayerClient", + "attributes": { + "name": "StarkNet.Baz.RelayerService.PluginRelayerClient", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet", + "attributes": { + "name": "StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Chain", + "attributes": { + "name": "StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Chain", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Relayer", + "attributes": { + "name": "StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Relayer", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Txm", + "attributes": { + "name": "StarkNet.Baz.RelayerService.PluginRelayerClient.PluginStarknet.Txm", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "TelemetryManager", + "attributes": { + "name": "TelemetryManager", + "status": "passing", + "output": "" + } + }, + { + "type": "checks", + "id": "WorkflowDBStore", + "attributes": { + "name": "WorkflowDBStore", + "status": "passing", + "output": "" + } + } + ] +} +-- out-unhealthy.json -- +{ + "data": [ + { + "type": "checks", + "id": "EVM.1.HeadTracker.HeadListener", + "attributes": { + "name": "EVM.1.HeadTracker.HeadListener", + "status": "failing", + "output": "Listener is not connected" + } + } + ] +} diff --git a/testdata/scripts/node/validate/defaults-override.txtar b/testdata/scripts/node/validate/defaults-override.txtar index 44f24dda47f..2acde4e13b6 100644 --- a/testdata/scripts/node/validate/defaults-override.txtar +++ b/testdata/scripts/node/validate/defaults-override.txtar @@ -405,6 +405,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar index 79fee5634d8..d061e4468ec 100644 --- a/testdata/scripts/node/validate/disk-based-logging-disabled.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-disabled.txtar @@ -388,6 +388,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true 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 2a64da5c274..b5609655977 100644 --- a/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar +++ b/testdata/scripts/node/validate/disk-based-logging-no-dir.txtar @@ -388,6 +388,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/testdata/scripts/node/validate/disk-based-logging.txtar b/testdata/scripts/node/validate/disk-based-logging.txtar index b6450111b71..217a78a7c3c 100644 --- a/testdata/scripts/node/validate/disk-based-logging.txtar +++ b/testdata/scripts/node/validate/disk-based-logging.txtar @@ -388,6 +388,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/testdata/scripts/node/validate/fallback-override.txtar b/testdata/scripts/node/validate/fallback-override.txtar index 58580d8203a..5cc001850d6 100644 --- a/testdata/scripts/node/validate/fallback-override.txtar +++ b/testdata/scripts/node/validate/fallback-override.txtar @@ -40,6 +40,9 @@ ResendAfterThreshold = '1m' [Transactions.AutoPurge] Enabled = false +[Transactions.TransactionManagerV2] +Enabled = false + [BalanceMonitor] Enabled = true @@ -479,6 +482,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/testdata/scripts/node/validate/invalid.txtar b/testdata/scripts/node/validate/invalid.txtar index 2247cf66e87..4d42750c96d 100644 --- a/testdata/scripts/node/validate/invalid.txtar +++ b/testdata/scripts/node/validate/invalid.txtar @@ -378,6 +378,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true diff --git a/testdata/scripts/node/validate/valid.txtar b/testdata/scripts/node/validate/valid.txtar index f431e853454..eef870f2280 100644 --- a/testdata/scripts/node/validate/valid.txtar +++ b/testdata/scripts/node/validate/valid.txtar @@ -385,6 +385,9 @@ ResendAfterThreshold = '1m0s' [EVM.Transactions.AutoPurge] Enabled = false +[EVM.Transactions.TransactionManagerV2] +Enabled = false + [EVM.BalanceMonitor] Enabled = true