diff --git a/.changeset/afraid-houses-learn.md b/.changeset/afraid-houses-learn.md new file mode 100644 index 00000000000..3d161965bae --- /dev/null +++ b/.changeset/afraid-houses-learn.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#updated use real contracts in ccipreader_tests where possible diff --git a/.changeset/thin-cats-try.md b/.changeset/thin-cats-try.md new file mode 100644 index 00000000000..e7934fe279a --- /dev/null +++ b/.changeset/thin-cats-try.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Add support for Mercury LLO streams to feeds service. #added diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index 9f6495c46f7..12cfcfb9256 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -942,12 +942,12 @@ runner-test-matrix: triggers: - PR E2E Core Tests - Nightly E2E Tests - test_cmd: cd integration-tests/smoke/ccip && go test ccip_test.go -timeout 12m -test.parallel=2 -count=1 -json + test_cmd: cd integration-tests/smoke/ccip && go test ccip_test.go -timeout 25m -test.parallel=2 -count=1 -json pyroscope_env: ci-smoke-ccipv1_6-evm-simulated test_env_vars: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 E2E_JD_VERSION: 0.6.0 - + - id: smoke/ccip/ccip_batching_test.go:* path: integration-tests/smoke/ccip/ccip_batching_test.go test_env_type: docker @@ -961,30 +961,30 @@ runner-test-matrix: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3 E2E_JD_VERSION: 0.6.0 - - id: smoke/ccip/ccip_usdc_test.go:* - path: integration-tests/smoke/ccip/ccip_usdc_test.go + - id: smoke/ccip/ccip_token_transfer_test.go:* + path: integration-tests/smoke/ccip/ccip_token_transfer_test.go test_env_type: docker runs_on: ubuntu-latest triggers: - PR E2E Core Tests - Nightly E2E Tests - test_cmd: cd integration-tests/smoke/ccip && go test ccip_usdc_test.go -timeout 18m -test.parallel=1 -count=1 -json + test_cmd: cd integration-tests/ && go test smoke/ccip/ccip_token_transfer_test.go -timeout 16m -test.parallel=1 -count=1 -json pyroscope_env: ci-smoke-ccipv1_6-evm-simulated test_env_vars: - E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3 + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 E2E_JD_VERSION: 0.6.0 - - id: smoke/ccip/fee_boosting_test.go:* - path: integration-tests/smoke/ccip/fee_boosting_test.go + - id: smoke/ccip/ccip_usdc_test.go:* + path: integration-tests/smoke/ccip/ccip_usdc_test.go test_env_type: docker runs_on: ubuntu-latest triggers: - PR E2E Core Tests - Nightly E2E Tests - test_cmd: cd integration-tests/smoke/ccip && go test fee_boosting_test.go -timeout 15m -test.parallel=1 -count=1 -json + test_cmd: cd integration-tests/smoke/ccip && go test ccip_usdc_test.go -timeout 18m -test.parallel=1 -count=1 -json pyroscope_env: ci-smoke-ccipv1_6-evm-simulated test_env_vars: - E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3 E2E_JD_VERSION: 0.6.0 - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_TwoMessagesOnTwoLanesIncludingBatching$ @@ -1017,21 +1017,20 @@ runner-test-matrix: E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e -# Enable after flaking issue is resolved -# - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughObservers$ -# path: integration-tests/smoke/ccip/ccip_rmn_test.go -# test_env_type: docker -# runs_on: ubuntu-latest -# triggers: -# - PR E2E Core Tests -# - Nightly E2E Tests -# test_cmd: cd integration-tests/smoke/ccip && go test -test.run ^TestRMN_NotEnoughObservers$ -timeout 12m -test.parallel=1 -count=1 -json -# pyroscope_env: ci-smoke-ccipv1_6-evm-simulated -# test_env_vars: -# E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 -# E2E_JD_VERSION: 0.6.0 -# E2E_RMN_RAGEPROXY_VERSION: master-f461a9e -# E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughObservers$ + path: integration-tests/smoke/ccip/ccip_rmn_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E Core Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/smoke/ccip && go test -test.run ^TestRMN_NotEnoughObservers$ -timeout 12m -test.parallel=1 -count=1 -json + pyroscope_env: ci-smoke-ccipv1_6-evm-simulated + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + E2E_JD_VERSION: 0.6.0 + E2E_RMN_RAGEPROXY_VERSION: master-f461a9e + E2E_RMN_AFN2PROXY_VERSION: master-f461a9e - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_DifferentSigners$ path: integration-tests/smoke/ccip/ccip_rmn_test.go @@ -1048,22 +1047,20 @@ runner-test-matrix: E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e -# Enable after flaking issue is resolved -# - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughSigners$ -# path: integration-tests/smoke/ccip/ccip_rmn_test.go -# test_env_type: docker -# runs_on: ubuntu-latest -# triggers: -# - PR E2E Core Tests -# - Nightly E2E Tests -# test_cmd: cd integration-tests/smoke/ccip && go test -test.run ^TestRMN_NotEnoughSigners$ -timeout 12m -test.parallel=1 -count=1 -json -# pyroscope_env: ci-smoke-ccipv1_6-evm-simulated -# test_env_vars: -# E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 -# E2E_JD_VERSION: 0.6.0 -# E2E_RMN_RAGEPROXY_VERSION: master-f461a9e -# E2E_RMN_AFN2PROXY_VERSION: master-f461a9e - + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughSigners$ + path: integration-tests/smoke/ccip/ccip_rmn_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E Core Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/smoke/ccip && go test -test.run ^TestRMN_NotEnoughSigners$ -timeout 12m -test.parallel=1 -count=1 -json + pyroscope_env: ci-smoke-ccipv1_6-evm-simulated + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + E2E_JD_VERSION: 0.6.0 + E2E_RMN_RAGEPROXY_VERSION: master-f461a9e + E2E_RMN_AFN2PROXY_VERSION: master-f461a9e - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_DifferentRmnNodesForDifferentChains$ path: integration-tests/smoke/ccip/ccip_rmn_test.go @@ -1080,6 +1077,35 @@ runner-test-matrix: E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_TwoMessagesOneSourceChainCursed$ + path: integration-tests/smoke/ccip/ccip_rmn_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E Core Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/smoke/ccip && go test -test.run ^TestRMN_TwoMessagesOneSourceChainCursed$ -timeout 12m -test.parallel=1 -count=1 -json + pyroscope_env: ci-smoke-ccipv1_6-evm-simulated + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + E2E_JD_VERSION: 0.6.0 + E2E_RMN_RAGEPROXY_VERSION: master-f461a9e + E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_GlobalCurseTwoMessagesOnTwoLanes$ + path: integration-tests/smoke/ccip/ccip_rmn_test.go + test_env_type: docker + runs_on: ubuntu-latest + triggers: + - PR E2E Core Tests + - Nightly E2E Tests + test_cmd: cd integration-tests/smoke/ccip && go test -test.run ^TestRMN_GlobalCurseTwoMessagesOnTwoLanes$ -timeout 12m -test.parallel=1 -count=1 -json + pyroscope_env: ci-smoke-ccipv1_6-evm-simulated + test_env_vars: + E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 + E2E_JD_VERSION: 0.6.0 + E2E_RMN_RAGEPROXY_VERSION: master-f461a9e + E2E_RMN_AFN2PROXY_VERSION: master-f461a9e # END: CCIPv1.6 tests diff --git a/.github/integration-in-memory-tests.yml b/.github/integration-in-memory-tests.yml index f97f3332eb7..c5e0f088afe 100644 --- a/.github/integration-in-memory-tests.yml +++ b/.github/integration-in-memory-tests.yml @@ -8,6 +8,14 @@ runner-test-matrix: # START: CCIPv1.6 tests + - id: smoke/ccip/ccip_fees_test.go:* + path: integration-tests/smoke/ccip/ccip_fees_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_fees_test.go -timeout 12m -test.parallel=2 -count=1 -json + - id: smoke/ccip/ccip_messaging_test.go:* path: integration-tests/smoke/ccip/ccip_messaging_test.go test_env_type: in-memory @@ -15,4 +23,21 @@ runner-test-matrix: triggers: - PR Integration CCIP Tests test_cmd: cd integration-tests/smoke/ccip && go test ccip_messaging_test.go -timeout 12m -test.parallel=2 -count=1 -json + + - id: smoke/ccip/ccip_fee_boosting_test.go:* + path: integration-tests/smoke/ccip/ccip_fee_boosting_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_fee_boosting_test.go -timeout 12m -test.parallel=2 -count=1 -json + + - id: contracts/ccipreader_test.go:* + path: integration-tests/contracts/ccipreader_test.go + test_env_type: in-memory + runs_on: ubuntu-latest + triggers: + - PR Integration CCIP Tests + test_cmd: cd integration-tests/contracts && go test ccipreader_test.go -timeout 5m -test.parallel=1 -count=1 -json + # END: CCIP tests diff --git a/.github/workflows/crib-integration-test.yml b/.github/workflows/crib-integration-test.yml index 5cd632dcecd..5dd24167ab0 100644 --- a/.github/workflows/crib-integration-test.yml +++ b/.github/workflows/crib-integration-test.yml @@ -76,7 +76,7 @@ jobs: echo $GITHUB_WORKSPACE - name: Deploy and validate CRIB Environment for Core - uses: smartcontractkit/.github/actions/crib-deploy-environment@c1c5e0952dfb1f7748cdad9789fd2a2ae8dc7348 # crib-deploy-environment@2.1.1 + uses: smartcontractkit/.github/actions/crib-deploy-environment@d4d9ce3fad044642d8d2f7ae5aca9f8c78b0073a # crib-deploy-environment@2.1.2 id: deploy-crib with: github-token: ${{ steps.token.outputs.access-token }} diff --git a/contracts/.changeset/chilly-rockets-share.md b/contracts/.changeset/chilly-rockets-share.md new file mode 100644 index 00000000000..ef5d3e454d7 --- /dev/null +++ b/contracts/.changeset/chilly-rockets-share.md @@ -0,0 +1,10 @@ +--- +'@chainlink/contracts': patch +--- + +Moves all audited L2EP contracts out from dev directory + + +PR issue: SHIP-3191 + +Solidity Review issue: SHIP-4050 \ No newline at end of file diff --git a/contracts/.changeset/mean-masks-poke.md b/contracts/.changeset/mean-masks-poke.md new file mode 100644 index 00000000000..00d8434e226 --- /dev/null +++ b/contracts/.changeset/mean-masks-poke.md @@ -0,0 +1,10 @@ +--- +'@chainlink/contracts': patch +--- + +#added new function to CCIPReaderTester getLatestPriceSequenceNumber + + +PR issue: CCIP-4239 + +Solidity Review issue: CCIP-3966 \ No newline at end of file diff --git a/contracts/src/v0.8/ccip/test/helpers/CCIPReaderTester.sol b/contracts/src/v0.8/ccip/test/helpers/CCIPReaderTester.sol index 38838f6acb2..d3567c6027d 100644 --- a/contracts/src/v0.8/ccip/test/helpers/CCIPReaderTester.sol +++ b/contracts/src/v0.8/ccip/test/helpers/CCIPReaderTester.sol @@ -10,6 +10,7 @@ contract CCIPReaderTester { mapping(uint64 sourceChainSelector => OffRamp.SourceChainConfig sourceChainConfig) internal s_sourceChainConfigs; mapping(uint64 destChainSelector => uint64 sequenceNumber) internal s_destChainSeqNrs; mapping(uint64 sourceChainSelector => mapping(bytes sender => uint64 nonce)) internal s_senderNonce; + uint64 private s_latestPriceSequenceNumber; /// @notice Gets the next sequence number to be used in the onRamp /// @param destChainSelector The destination chain selector @@ -52,6 +53,19 @@ contract CCIPReaderTester { s_sourceChainConfigs[sourceChainSelector] = sourceChainConfig; } + /// @notice sets the sequence number of the last price update. + function setLatestPriceSequenceNumber( + uint64 seqNr + ) external { + s_latestPriceSequenceNumber = seqNr; + } + + /// @notice Returns the sequence number of the last price update. + /// @return sequenceNumber The latest price update sequence number. + function getLatestPriceSequenceNumber() external view returns (uint64) { + return s_latestPriceSequenceNumber; + } + function emitCCIPMessageSent(uint64 destChainSelector, Internal.EVM2AnyRampMessage memory message) external { emit OnRamp.CCIPMessageSent(destChainSelector, message.header.sequenceNumber, message); } diff --git a/contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol b/contracts/src/v0.8/l2ep/CrossDomainDelegateForwarder.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/CrossDomainDelegateForwarder.sol rename to contracts/src/v0.8/l2ep/CrossDomainDelegateForwarder.sol diff --git a/contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/CrossDomainForwarder.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/CrossDomainForwarder.sol rename to contracts/src/v0.8/l2ep/CrossDomainForwarder.sol diff --git a/contracts/src/v0.8/l2ep/dev/CrossDomainOwnable.sol b/contracts/src/v0.8/l2ep/CrossDomainOwnable.sol similarity index 96% rename from contracts/src/v0.8/l2ep/dev/CrossDomainOwnable.sol rename to contracts/src/v0.8/l2ep/CrossDomainOwnable.sol index c85762b6fca..7e1c0d1adb9 100644 --- a/contracts/src/v0.8/l2ep/dev/CrossDomainOwnable.sol +++ b/contracts/src/v0.8/l2ep/CrossDomainOwnable.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol"; import {ICrossDomainOwnable} from "./interfaces/ICrossDomainOwnable.sol"; /** diff --git a/contracts/src/v0.8/l2ep/dev/Flags.sol b/contracts/src/v0.8/l2ep/Flags.sol similarity index 95% rename from contracts/src/v0.8/l2ep/dev/Flags.sol rename to contracts/src/v0.8/l2ep/Flags.sol index 2dc030d7d59..2ac35be4ce7 100644 --- a/contracts/src/v0.8/l2ep/dev/Flags.sol +++ b/contracts/src/v0.8/l2ep/Flags.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {SimpleReadAccessController} from "../../shared/access/SimpleReadAccessController.sol"; -import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol"; -import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {SimpleReadAccessController} from "../shared/access/SimpleReadAccessController.sol"; +import {AccessControllerInterface} from "../shared/interfaces/AccessControllerInterface.sol"; +import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol"; /* dev dependencies - to be re/moved after audit */ import {IFlags} from "./interfaces/IFlags.sol"; diff --git a/contracts/src/v0.8/l2ep/README.md b/contracts/src/v0.8/l2ep/README.md index 21a537c4fe7..83eb6fe9372 100644 --- a/contracts/src/v0.8/l2ep/README.md +++ b/contracts/src/v0.8/l2ep/README.md @@ -11,7 +11,8 @@ Emergency Protocol (L2EP) contracts. It is organized as follows: ## The `/dev` Folder -The `/dev` folder contains subfolders for each chain that +The `/dev` folder contains contracts that has not yet been audited. +The root folder contains subfolders for each chain that has an L2EP solution implemented for it (e.g. `/scroll`, `/arbitrum`, `/optimism`). It also contains a subfolder named `/interfaces`, which stores shared interface types between all the supported diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/arbitrum/ArbitrumCrossDomainForwarder.sol similarity index 89% rename from contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainForwarder.sol rename to contracts/src/v0.8/l2ep/arbitrum/ArbitrumCrossDomainForwarder.sol index 0db657ee71c..e3ef117d23e 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainForwarder.sol +++ b/contracts/src/v0.8/l2ep/arbitrum/ArbitrumCrossDomainForwarder.sol @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; // solhint-disable-next-line no-unused-import import {IForwarder} from "../interfaces/IForwarder.sol"; import {CrossDomainForwarder} from "../CrossDomainForwarder.sol"; import {CrossDomainOwnable} from "../CrossDomainOwnable.sol"; -import {AddressAliasHelper} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {AddressAliasHelper} from "../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; /** * @title ArbitrumCrossDomainForwarder - L1 xDomain account representation diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainGovernor.sol b/contracts/src/v0.8/l2ep/arbitrum/ArbitrumCrossDomainGovernor.sol similarity index 93% rename from contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainGovernor.sol rename to contracts/src/v0.8/l2ep/arbitrum/ArbitrumCrossDomainGovernor.sol index 60d9cc52666..f5ed8d7a9d2 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumCrossDomainGovernor.sol +++ b/contracts/src/v0.8/l2ep/arbitrum/ArbitrumCrossDomainGovernor.sol @@ -2,14 +2,14 @@ pragma solidity ^0.8.0; // solhint-disable-next-line no-unused-import -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; // solhint-disable-next-line no-unused-import import {IForwarder} from "../interfaces/IForwarder.sol"; import {IDelegateForwarder} from "../interfaces/IDelegateForwarder.sol"; import {ArbitrumCrossDomainForwarder} from "./ArbitrumCrossDomainForwarder.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; /** * @title ArbitrumCrossDomainGovernor - L1 xDomain account representation (with delegatecall support) for Arbitrum diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/arbitrum/ArbitrumSequencerUptimeFeed.sol similarity index 94% rename from contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol rename to contracts/src/v0.8/l2ep/arbitrum/ArbitrumSequencerUptimeFeed.sol index 678bef3a853..1ba6b9b54b3 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/l2ep/arbitrum/ArbitrumSequencerUptimeFeed.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {AddressAliasHelper} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; -import {AggregatorInterface} from "../../../shared/interfaces/AggregatorInterface.sol"; -import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; -import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {AddressAliasHelper} from "../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; +import {AggregatorInterface} from "../../shared/interfaces/AggregatorInterface.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; +import {AggregatorV2V3Interface} from "../../shared/interfaces/AggregatorV2V3Interface.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; import {IFlags} from "../interfaces/IFlags.sol"; import {ISequencerUptimeFeed} from "../interfaces/ISequencerUptimeFeed.sol"; -import {SimpleReadAccessController} from "../../../shared/access/SimpleReadAccessController.sol"; +import {SimpleReadAccessController} from "../../shared/access/SimpleReadAccessController.sol"; /** * @title ArbitrumSequencerUptimeFeed - L2 sequencer uptime status aggregator diff --git a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol b/contracts/src/v0.8/l2ep/arbitrum/ArbitrumValidator.sol similarity index 95% rename from contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol rename to contracts/src/v0.8/l2ep/arbitrum/ArbitrumValidator.sol index 05f9349eb62..3d30f38981c 100644 --- a/contracts/src/v0.8/l2ep/dev/arbitrum/ArbitrumValidator.sol +++ b/contracts/src/v0.8/l2ep/arbitrum/ArbitrumValidator.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {AggregatorValidatorInterface} from "../../../shared/interfaces/AggregatorValidatorInterface.sol"; -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; -import {AccessControllerInterface} from "../../../shared/interfaces/AccessControllerInterface.sol"; -import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; +import {AggregatorValidatorInterface} from "../../shared/interfaces/AggregatorValidatorInterface.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol"; +import {SimpleWriteAccessController} from "../../shared/access/SimpleWriteAccessController.sol"; /* ./dev dependencies - to be moved from ./dev after audit */ import {ISequencerUptimeFeed} from "../interfaces/ISequencerUptimeFeed.sol"; import {IArbitrumDelayedInbox} from "../interfaces/IArbitrumDelayedInbox.sol"; -import {AddressAliasHelper} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; -import {ArbSys} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {AddressAliasHelper} from "../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; +import {ArbSys} from "../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; /** * @title ArbitrumValidator - makes xDomain L2 Flags contract call (using L2 xDomain Forwarder contract) diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/IArbitrumDelayedInbox.sol b/contracts/src/v0.8/l2ep/interfaces/IArbitrumDelayedInbox.sol similarity index 84% rename from contracts/src/v0.8/l2ep/dev/interfaces/IArbitrumDelayedInbox.sol rename to contracts/src/v0.8/l2ep/interfaces/IArbitrumDelayedInbox.sol index e18efd65ad2..802ec06b168 100644 --- a/contracts/src/v0.8/l2ep/dev/interfaces/IArbitrumDelayedInbox.sol +++ b/contracts/src/v0.8/l2ep/interfaces/IArbitrumDelayedInbox.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {IInbox} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/bridge/interfaces/IInbox.sol"; +import {IInbox} from "../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/bridge/interfaces/IInbox.sol"; /** * @notice This interface extends Arbitrum's IInbox interface to include diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/ICrossDomainOwnable.sol b/contracts/src/v0.8/l2ep/interfaces/ICrossDomainOwnable.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/interfaces/ICrossDomainOwnable.sol rename to contracts/src/v0.8/l2ep/interfaces/ICrossDomainOwnable.sol diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/IDelegateForwarder.sol b/contracts/src/v0.8/l2ep/interfaces/IDelegateForwarder.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/interfaces/IDelegateForwarder.sol rename to contracts/src/v0.8/l2ep/interfaces/IDelegateForwarder.sol diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/IFlags.sol b/contracts/src/v0.8/l2ep/interfaces/IFlags.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/interfaces/IFlags.sol rename to contracts/src/v0.8/l2ep/interfaces/IFlags.sol diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/IForwarder.sol b/contracts/src/v0.8/l2ep/interfaces/IForwarder.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/interfaces/IForwarder.sol rename to contracts/src/v0.8/l2ep/interfaces/IForwarder.sol diff --git a/contracts/src/v0.8/l2ep/dev/interfaces/ISequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/interfaces/ISequencerUptimeFeed.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/interfaces/ISequencerUptimeFeed.sol rename to contracts/src/v0.8/l2ep/interfaces/ISequencerUptimeFeed.sol diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/optimism/OptimismCrossDomainForwarder.sol similarity index 91% rename from contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainForwarder.sol rename to contracts/src/v0.8/l2ep/optimism/OptimismCrossDomainForwarder.sol index 1d037886960..f2b4fafc32d 100644 --- a/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainForwarder.sol +++ b/contracts/src/v0.8/l2ep/optimism/OptimismCrossDomainForwarder.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; // solhint-disable-next-line no-unused-import import {IForwarder} from "../interfaces/IForwarder.sol"; @@ -9,8 +9,8 @@ import {IForwarder} from "../interfaces/IForwarder.sol"; import {CrossDomainForwarder} from "../CrossDomainForwarder.sol"; import {CrossDomainOwnable} from "../CrossDomainOwnable.sol"; -import {iOVM_CrossDomainMessenger} from "../../../vendor/@eth-optimism/contracts/v0.4.7/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_CrossDomainMessenger.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {iOVM_CrossDomainMessenger} from "../../vendor/@eth-optimism/contracts/v0.4.7/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_CrossDomainMessenger.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; /** * @title OptimismCrossDomainForwarder - L1 xDomain account representation diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainGovernor.sol b/contracts/src/v0.8/l2ep/optimism/OptimismCrossDomainGovernor.sol similarity index 91% rename from contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainGovernor.sol rename to contracts/src/v0.8/l2ep/optimism/OptimismCrossDomainGovernor.sol index 6a41bd98f03..61bed5a6903 100644 --- a/contracts/src/v0.8/l2ep/dev/optimism/OptimismCrossDomainGovernor.sol +++ b/contracts/src/v0.8/l2ep/optimism/OptimismCrossDomainGovernor.sol @@ -7,8 +7,8 @@ import {IForwarder} from "../interfaces/IForwarder.sol"; import {OptimismCrossDomainForwarder} from "./OptimismCrossDomainForwarder.sol"; -import {iOVM_CrossDomainMessenger} from "../../../vendor/@eth-optimism/contracts/v0.4.7/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_CrossDomainMessenger.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {iOVM_CrossDomainMessenger} from "../../vendor/@eth-optimism/contracts/v0.4.7/contracts/optimistic-ethereum/iOVM/bridge/messaging/iOVM_CrossDomainMessenger.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; /** * @title OptimismCrossDomainGovernor - L1 xDomain account representation (with delegatecall support) for Optimism diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/optimism/OptimismSequencerUptimeFeed.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/optimism/OptimismSequencerUptimeFeed.sol rename to contracts/src/v0.8/l2ep/optimism/OptimismSequencerUptimeFeed.sol diff --git a/contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol b/contracts/src/v0.8/l2ep/optimism/OptimismValidator.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/optimism/OptimismValidator.sol rename to contracts/src/v0.8/l2ep/optimism/OptimismValidator.sol diff --git a/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainForwarder.sol b/contracts/src/v0.8/l2ep/scroll/ScrollCrossDomainForwarder.sol similarity index 94% rename from contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainForwarder.sol rename to contracts/src/v0.8/l2ep/scroll/ScrollCrossDomainForwarder.sol index c70bc794afb..35e8b4827d3 100644 --- a/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainForwarder.sol +++ b/contracts/src/v0.8/l2ep/scroll/ScrollCrossDomainForwarder.sol @@ -1,14 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; import {IForwarder} from "../interfaces/IForwarder.sol"; import {CrossDomainForwarder} from "../CrossDomainForwarder.sol"; import {CrossDomainOwnable} from "../CrossDomainOwnable.sol"; import {IScrollMessenger} from "@scroll-tech/contracts/libraries/IScrollMessenger.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; /// @title ScrollCrossDomainForwarder - L1 xDomain account representation /// @notice L2 Contract which receives messages from a specific L1 address and transparently forwards them to the destination. diff --git a/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainGovernor.sol b/contracts/src/v0.8/l2ep/scroll/ScrollCrossDomainGovernor.sol similarity index 96% rename from contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainGovernor.sol rename to contracts/src/v0.8/l2ep/scroll/ScrollCrossDomainGovernor.sol index dae621e9b08..7170cc2e491 100644 --- a/contracts/src/v0.8/l2ep/dev/scroll/ScrollCrossDomainGovernor.sol +++ b/contracts/src/v0.8/l2ep/scroll/ScrollCrossDomainGovernor.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; import {IDelegateForwarder} from "../interfaces/IDelegateForwarder.sol"; // solhint-disable-next-line no-unused-import import {IForwarder} from "../interfaces/IForwarder.sol"; @@ -10,7 +10,7 @@ import {CrossDomainForwarder} from "../CrossDomainForwarder.sol"; import {CrossDomainOwnable} from "../CrossDomainOwnable.sol"; import {IScrollMessenger} from "@scroll-tech/contracts/libraries/IScrollMessenger.sol"; -import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; /// @title ScrollCrossDomainGovernor - L1 xDomain account representation (with delegatecall support) for Scroll /// @notice L2 Contract which receives messages from a specific L1 address and transparently forwards them to the destination. diff --git a/contracts/src/v0.8/l2ep/dev/scroll/ScrollSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/scroll/ScrollSequencerUptimeFeed.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/scroll/ScrollSequencerUptimeFeed.sol rename to contracts/src/v0.8/l2ep/scroll/ScrollSequencerUptimeFeed.sol diff --git a/contracts/src/v0.8/l2ep/dev/scroll/ScrollValidator.sol b/contracts/src/v0.8/l2ep/scroll/ScrollValidator.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/scroll/ScrollValidator.sol rename to contracts/src/v0.8/l2ep/scroll/ScrollValidator.sol diff --git a/contracts/src/v0.8/l2ep/dev/shared/BaseSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/shared/BaseSequencerUptimeFeed.sol similarity index 94% rename from contracts/src/v0.8/l2ep/dev/shared/BaseSequencerUptimeFeed.sol rename to contracts/src/v0.8/l2ep/shared/BaseSequencerUptimeFeed.sol index 15c20504569..344270fcff8 100644 --- a/contracts/src/v0.8/l2ep/dev/shared/BaseSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/l2ep/shared/BaseSequencerUptimeFeed.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; -import {AggregatorInterface} from "../../../shared/interfaces/AggregatorInterface.sol"; -import {AggregatorV3Interface} from "../../../shared/interfaces/AggregatorV3Interface.sol"; -import {AggregatorV2V3Interface} from "../../../shared/interfaces/AggregatorV2V3Interface.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {AggregatorInterface} from "../../shared/interfaces/AggregatorInterface.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; +import {AggregatorV2V3Interface} from "../../shared/interfaces/AggregatorV2V3Interface.sol"; import {ISequencerUptimeFeed} from "./../interfaces/ISequencerUptimeFeed.sol"; -import {SimpleReadAccessController} from "../../../shared/access/SimpleReadAccessController.sol"; +import {SimpleReadAccessController} from "../../shared/access/SimpleReadAccessController.sol"; /// @title L2 sequencer uptime status aggregator /// @notice L2 contract that receives status updates from a specific L1 address, diff --git a/contracts/src/v0.8/l2ep/dev/shared/BaseValidator.sol b/contracts/src/v0.8/l2ep/shared/BaseValidator.sol similarity index 88% rename from contracts/src/v0.8/l2ep/dev/shared/BaseValidator.sol rename to contracts/src/v0.8/l2ep/shared/BaseValidator.sol index 33df388972f..8b38da391ec 100644 --- a/contracts/src/v0.8/l2ep/dev/shared/BaseValidator.sol +++ b/contracts/src/v0.8/l2ep/shared/BaseValidator.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {AggregatorValidatorInterface} from "../../../shared/interfaces/AggregatorValidatorInterface.sol"; -import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol"; +import {AggregatorValidatorInterface} from "../../shared/interfaces/AggregatorValidatorInterface.sol"; +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; -import {SimpleWriteAccessController} from "../../../shared/access/SimpleWriteAccessController.sol"; +import {SimpleWriteAccessController} from "../../shared/access/SimpleWriteAccessController.sol"; abstract contract BaseValidator is SimpleWriteAccessController, AggregatorValidatorInterface, ITypeAndVersion { /// @notice emitted when gas cost to spend on L2 is updated diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainForwarder.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainForwarder.t.sol index cff9b953e2e..e0a76a2b37a 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainForwarder.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainForwarder.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {ArbitrumCrossDomainForwarder} from "../../../dev/arbitrum/ArbitrumCrossDomainForwarder.sol"; +import {ArbitrumCrossDomainForwarder} from "../../../arbitrum/ArbitrumCrossDomainForwarder.sol"; import {Greeter} from "../../../../tests/Greeter.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainGovernor.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainGovernor.t.sol index 610f49f16c4..746da3d1cef 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainGovernor.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumCrossDomainGovernor.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {ArbitrumCrossDomainGovernor} from "../../../dev/arbitrum/ArbitrumCrossDomainGovernor.sol"; +import {ArbitrumCrossDomainGovernor} from "../../../arbitrum/ArbitrumCrossDomainGovernor.sol"; import {Greeter} from "../../../../tests/Greeter.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumSequencerUptimeFeed.t.sol index e308ead3432..deaa81977b7 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumSequencerUptimeFeed.t.sol @@ -2,10 +2,10 @@ pragma solidity 0.8.24; import {SimpleWriteAccessController} from "../../../../shared/access/SimpleWriteAccessController.sol"; -import {ArbitrumSequencerUptimeFeed} from "../../../dev/arbitrum/ArbitrumSequencerUptimeFeed.sol"; +import {ArbitrumSequencerUptimeFeed} from "../../../arbitrum/ArbitrumSequencerUptimeFeed.sol"; import {MockAggregatorV2V3} from "../../mocks/MockAggregatorV2V3.sol"; import {FeedConsumer} from "../../../../tests/FeedConsumer.sol"; -import {Flags} from "../../../dev/Flags.sol"; +import {Flags} from "../../../Flags.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; contract ArbitrumSequencerUptimeFeedTest is L2EPTest { diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumValidator.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumValidator.t.sol index ab872991749..95278e644b1 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumValidator.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/arbitrum/ArbitrumValidator.t.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.24; import {AccessControllerInterface} from "../../../../shared/interfaces/AccessControllerInterface.sol"; import {SimpleWriteAccessController} from "../../../../shared/access/SimpleWriteAccessController.sol"; -import {ArbitrumSequencerUptimeFeed} from "../../../dev/arbitrum/ArbitrumSequencerUptimeFeed.sol"; -import {ArbitrumValidator} from "../../../dev/arbitrum/ArbitrumValidator.sol"; +import {ArbitrumSequencerUptimeFeed} from "../../../arbitrum/ArbitrumSequencerUptimeFeed.sol"; +import {ArbitrumValidator} from "../../../arbitrum/ArbitrumValidator.sol"; import {MockArbitrumInbox} from "../../../../tests/MockArbitrumInbox.sol"; import {MockAggregatorV2V3} from "../../mocks/MockAggregatorV2V3.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainForwarder.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainForwarder.t.sol index c0e82ab8d5e..28d70fa35a5 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainForwarder.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainForwarder.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {OptimismCrossDomainForwarder} from "../../../dev/optimism/OptimismCrossDomainForwarder.sol"; +import {OptimismCrossDomainForwarder} from "../../../optimism/OptimismCrossDomainForwarder.sol"; import {MockOVMCrossDomainMessenger} from "../../mocks/optimism/MockOVMCrossDomainMessenger.sol"; import {Greeter} from "../../../../tests/Greeter.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainGovernor.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainGovernor.t.sol index 8f8fb9d7e7c..57f79124512 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainGovernor.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismCrossDomainGovernor.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {OptimismCrossDomainGovernor} from "../../../dev/optimism/OptimismCrossDomainGovernor.sol"; +import {OptimismCrossDomainGovernor} from "../../../optimism/OptimismCrossDomainGovernor.sol"; import {MockOVMCrossDomainMessenger} from "../../mocks/optimism/MockOVMCrossDomainMessenger.sol"; import {Greeter} from "../../../../tests/Greeter.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol index eec9657ac14..daf50962a1e 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismSequencerUptimeFeed.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.24; import {MockOptimismL1CrossDomainMessenger} from "../../../../tests/MockOptimismL1CrossDomainMessenger.sol"; import {MockOptimismL2CrossDomainMessenger} from "../../../../tests/MockOptimismL2CrossDomainMessenger.sol"; -import {OptimismSequencerUptimeFeed} from "../../../dev/optimism/OptimismSequencerUptimeFeed.sol"; -import {BaseSequencerUptimeFeed} from "../../../dev/shared/BaseSequencerUptimeFeed.sol"; +import {OptimismSequencerUptimeFeed} from "../../../optimism/OptimismSequencerUptimeFeed.sol"; +import {BaseSequencerUptimeFeed} from "../../../shared/BaseSequencerUptimeFeed.sol"; import {FeedConsumer} from "../../../../tests/FeedConsumer.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismValidator.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismValidator.t.sol index 59395bf5d8b..ba9e3f872f1 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismValidator.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/optimism/OptimismValidator.t.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {ISequencerUptimeFeed} from "../../../dev/interfaces/ISequencerUptimeFeed.sol"; +import {ISequencerUptimeFeed} from "../../../interfaces/ISequencerUptimeFeed.sol"; import {MockOptimismL1CrossDomainMessenger} from "../../../../tests/MockOptimismL1CrossDomainMessenger.sol"; import {MockOptimismL2CrossDomainMessenger} from "../../../../tests/MockOptimismL2CrossDomainMessenger.sol"; -import {OptimismSequencerUptimeFeed} from "../../../dev/optimism/OptimismSequencerUptimeFeed.sol"; -import {OptimismValidator} from "../../../dev/optimism/OptimismValidator.sol"; +import {OptimismSequencerUptimeFeed} from "../../../optimism/OptimismSequencerUptimeFeed.sol"; +import {OptimismValidator} from "../../../optimism/OptimismValidator.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; contract OptimismValidatorTest is L2EPTest { diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainForwarder.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainForwarder.t.sol index e34e84f4006..b0ef7df22c1 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainForwarder.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainForwarder.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.24; import {MockScrollCrossDomainMessenger} from "../../mocks/scroll/MockScrollCrossDomainMessenger.sol"; -import {ScrollCrossDomainForwarder} from "../../../dev/scroll/ScrollCrossDomainForwarder.sol"; +import {ScrollCrossDomainForwarder} from "../../../scroll/ScrollCrossDomainForwarder.sol"; import {Greeter} from "../../../../tests/Greeter.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainGovernor.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainGovernor.t.sol index 8c3d56d1560..5eefaddab70 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainGovernor.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollCrossDomainGovernor.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.24; import {MockScrollCrossDomainMessenger} from "../../mocks/scroll/MockScrollCrossDomainMessenger.sol"; -import {ScrollCrossDomainGovernor} from "../../../dev/scroll/ScrollCrossDomainGovernor.sol"; +import {ScrollCrossDomainGovernor} from "../../../scroll/ScrollCrossDomainGovernor.sol"; import {Greeter} from "../../../../tests/Greeter.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol index 0968c69415f..5e4d8a7a20f 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollSequencerUptimeFeed.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.24; import {MockScrollL1CrossDomainMessenger} from "../../mocks/scroll/MockScrollL1CrossDomainMessenger.sol"; import {MockScrollL2CrossDomainMessenger} from "../../mocks/scroll/MockScrollL2CrossDomainMessenger.sol"; -import {ScrollSequencerUptimeFeed} from "../../../dev/scroll/ScrollSequencerUptimeFeed.sol"; -import {BaseSequencerUptimeFeed} from "../../../dev/shared/BaseSequencerUptimeFeed.sol"; +import {ScrollSequencerUptimeFeed} from "../../../scroll/ScrollSequencerUptimeFeed.sol"; +import {BaseSequencerUptimeFeed} from "../../../shared/BaseSequencerUptimeFeed.sol"; import {FeedConsumer} from "../../../../tests/FeedConsumer.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollValidator.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollValidator.t.sol index 3d5298d5184..1a0bcc856f2 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollValidator.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/scroll/ScrollValidator.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import {ISequencerUptimeFeed} from "../../../dev/interfaces/ISequencerUptimeFeed.sol"; +import {ISequencerUptimeFeed} from "../../../interfaces/ISequencerUptimeFeed.sol"; import {MockScrollL1CrossDomainMessenger} from "../../mocks/scroll/MockScrollL1CrossDomainMessenger.sol"; import {MockScrollL2CrossDomainMessenger} from "../../mocks/scroll/MockScrollL2CrossDomainMessenger.sol"; import {MockScrollL1MessageQueue} from "../../mocks/scroll/MockScrollL1MessageQueue.sol"; -import {ScrollSequencerUptimeFeed} from "../../../dev/scroll/ScrollSequencerUptimeFeed.sol"; -import {ScrollValidator} from "../../../dev/scroll/ScrollValidator.sol"; +import {ScrollSequencerUptimeFeed} from "../../../scroll/ScrollSequencerUptimeFeed.sol"; +import {ScrollValidator} from "../../../scroll/ScrollValidator.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; contract ScrollValidatorTest is L2EPTest { diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncSequencerUptimeFeed.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncSequencerUptimeFeed.t.sol index 6d90b3973e9..5b319d32407 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncSequencerUptimeFeed.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncSequencerUptimeFeed.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.24; import {AddressAliasHelper} from "../../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; -import {ZKSyncSequencerUptimeFeed} from "../../../dev/zksync/ZKSyncSequencerUptimeFeed.sol"; -import {BaseSequencerUptimeFeed} from "../../../dev/shared/BaseSequencerUptimeFeed.sol"; +import {ZKSyncSequencerUptimeFeed} from "../../../zksync/ZKSyncSequencerUptimeFeed.sol"; +import {BaseSequencerUptimeFeed} from "../../../shared/BaseSequencerUptimeFeed.sol"; import {FeedConsumer} from "../../../../tests/FeedConsumer.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; diff --git a/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncValidator.t.sol b/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncValidator.t.sol index 0bea147c8cd..1a0f16d6ae0 100644 --- a/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncValidator.t.sol +++ b/contracts/src/v0.8/l2ep/test/v1_0_0/zksync/ZKSyncValidator.t.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.24; import {MockBridgehub} from "../../mocks/zksync/MockZKSyncL1Bridge.sol"; -import {ISequencerUptimeFeed} from "../../../dev/interfaces/ISequencerUptimeFeed.sol"; -import {ZKSyncValidator} from "../../../dev/zksync/ZKSyncValidator.sol"; -import {BaseValidator} from "../../../dev/shared/BaseValidator.sol"; +import {ISequencerUptimeFeed} from "../../../interfaces/ISequencerUptimeFeed.sol"; +import {ZKSyncValidator} from "../../../zksync/ZKSyncValidator.sol"; +import {BaseValidator} from "../../../shared/BaseValidator.sol"; import {L2EPTest} from "../L2EPTest.t.sol"; contract ZKSyncValidatorTest is L2EPTest { diff --git a/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncSequencerUptimeFeed.sol b/contracts/src/v0.8/l2ep/zksync/ZKSyncSequencerUptimeFeed.sol similarity index 89% rename from contracts/src/v0.8/l2ep/dev/zksync/ZKSyncSequencerUptimeFeed.sol rename to contracts/src/v0.8/l2ep/zksync/ZKSyncSequencerUptimeFeed.sol index 0074a0277d1..cb520d2d0ac 100644 --- a/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncSequencerUptimeFeed.sol +++ b/contracts/src/v0.8/l2ep/zksync/ZKSyncSequencerUptimeFeed.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import {BaseSequencerUptimeFeed} from "../shared/BaseSequencerUptimeFeed.sol"; -import {AddressAliasHelper} from "../../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; +import {AddressAliasHelper} from "../../vendor/arb-bridge-eth/v0.8.0-custom/contracts/libraries/AddressAliasHelper.sol"; /// @title ZKSyncSequencerUptimeFeed - L2 sequencer uptime status aggregator /// @notice L2 contract that receives status updates from a specific L1 address, diff --git a/contracts/src/v0.8/l2ep/dev/zksync/ZKSyncValidator.sol b/contracts/src/v0.8/l2ep/zksync/ZKSyncValidator.sol similarity index 100% rename from contracts/src/v0.8/l2ep/dev/zksync/ZKSyncValidator.sol rename to contracts/src/v0.8/l2ep/zksync/ZKSyncValidator.sol diff --git a/core/capabilities/don_notifier.go b/core/capabilities/don_notifier.go new file mode 100644 index 00000000000..4edb38d3661 --- /dev/null +++ b/core/capabilities/don_notifier.go @@ -0,0 +1,43 @@ +package capabilities + +import ( + "context" + "sync" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" +) + +type DonNotifier struct { + mu sync.Mutex + don capabilities.DON + notified bool + ch chan struct{} +} + +func NewDonNotifier() *DonNotifier { + return &DonNotifier{ + ch: make(chan struct{}), + } +} + +func (n *DonNotifier) NotifyDonSet(don capabilities.DON) { + n.mu.Lock() + defer n.mu.Unlock() + if !n.notified { + n.don = don + n.notified = true + close(n.ch) + } +} + +func (n *DonNotifier) WaitForDon(ctx context.Context) (capabilities.DON, error) { + select { + case <-ctx.Done(): + return capabilities.DON{}, ctx.Err() + case <-n.ch: + } + <-n.ch + n.mu.Lock() + defer n.mu.Unlock() + return n.don, nil +} diff --git a/core/capabilities/don_notifier_test.go b/core/capabilities/don_notifier_test.go new file mode 100644 index 00000000000..f37931259ba --- /dev/null +++ b/core/capabilities/don_notifier_test.go @@ -0,0 +1,49 @@ +package capabilities_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/v2/core/capabilities" +) + +func TestDonNotifier_WaitForDon(t *testing.T) { + notifier := capabilities.NewDonNotifier() + don := commoncap.DON{ + ID: 1, + } + + go func() { + time.Sleep(100 * time.Millisecond) + notifier.NotifyDonSet(don) + }() + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + result, err := notifier.WaitForDon(ctx) + require.NoError(t, err) + assert.Equal(t, don, result) + + result, err = notifier.WaitForDon(ctx) + require.NoError(t, err) + assert.Equal(t, don, result) +} + +func TestDonNotifier_WaitForDon_ContextTimeout(t *testing.T) { + notifier := capabilities.NewDonNotifier() + + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) + defer cancel() + + _, err := notifier.WaitForDon(ctx) + require.Error(t, err) + assert.Equal(t, context.DeadlineExceeded, err) +} diff --git a/core/capabilities/launcher.go b/core/capabilities/launcher.go index be06dcf60c1..97aea5d3c8c 100644 --- a/core/capabilities/launcher.go +++ b/core/capabilities/launcher.go @@ -43,11 +43,12 @@ var defaultStreamConfig = p2ptypes.StreamConfig{ type launcher struct { services.StateMachine - lggr logger.Logger - peerWrapper p2ptypes.PeerWrapper - dispatcher remotetypes.Dispatcher - registry *Registry - subServices []services.Service + lggr logger.Logger + peerWrapper p2ptypes.PeerWrapper + dispatcher remotetypes.Dispatcher + registry *Registry + subServices []services.Service + workflowDonNotifier donNotifier } func unmarshalCapabilityConfig(data []byte) (capabilities.CapabilityConfiguration, error) { @@ -86,18 +87,24 @@ func unmarshalCapabilityConfig(data []byte) (capabilities.CapabilityConfiguratio }, nil } +type donNotifier interface { + NotifyDonSet(don capabilities.DON) +} + func NewLauncher( lggr logger.Logger, peerWrapper p2ptypes.PeerWrapper, dispatcher remotetypes.Dispatcher, registry *Registry, + workflowDonNotifier donNotifier, ) *launcher { return &launcher{ - lggr: lggr.Named("CapabilitiesLauncher"), - peerWrapper: peerWrapper, - dispatcher: dispatcher, - registry: registry, - subServices: []services.Service{}, + lggr: lggr.Named("CapabilitiesLauncher"), + peerWrapper: peerWrapper, + dispatcher: dispatcher, + registry: registry, + subServices: []services.Service{}, + workflowDonNotifier: workflowDonNotifier, } } @@ -215,6 +222,8 @@ func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegist return errors.New("invariant violation: node is part of more than one workflowDON") } + w.workflowDonNotifier.NotifyDonSet(myDON.DON) + for _, rcd := range remoteCapabilityDONs { err := w.addRemoteCapabilities(ctx, myDON, rcd, state) if err != nil { diff --git a/core/capabilities/launcher_test.go b/core/capabilities/launcher_test.go index 013463bfdbb..c130f9833d9 100644 --- a/core/capabilities/launcher_test.go +++ b/core/capabilities/launcher_test.go @@ -33,6 +33,12 @@ import ( var _ capabilities.TriggerCapability = (*mockTrigger)(nil) +type mockDonNotifier struct { +} + +func (m *mockDonNotifier) NotifyDonSet(don capabilities.DON) { +} + type mockTrigger struct { capabilities.CapabilityInfo } @@ -196,6 +202,7 @@ func TestLauncher(t *testing.T) { wrapper, dispatcher, registry, + &mockDonNotifier{}, ) dispatcher.On("SetReceiver", fullTriggerCapID, dID, mock.AnythingOfType("*remote.triggerPublisher")).Return(nil) @@ -305,6 +312,7 @@ func TestLauncher(t *testing.T) { wrapper, dispatcher, registry, + &mockDonNotifier{}, ) err = launcher.Launch(ctx, state) @@ -409,6 +417,7 @@ func TestLauncher(t *testing.T) { wrapper, dispatcher, registry, + &mockDonNotifier{}, ) err = launcher.Launch(ctx, state) @@ -600,6 +609,7 @@ func TestLauncher_RemoteTriggerModeAggregatorShim(t *testing.T) { wrapper, dispatcher, registry, + &mockDonNotifier{}, ) dispatcher.On("SetReceiver", fullTriggerCapID, capDonID, mock.AnythingOfType("*remote.triggerSubscriber")).Return(nil) @@ -752,6 +762,7 @@ func TestSyncer_IgnoresCapabilitiesForPrivateDON(t *testing.T) { wrapper, dispatcher, registry, + &mockDonNotifier{}, ) // If the DON were public, this would fail with two errors: @@ -917,6 +928,7 @@ func TestLauncher_WiresUpClientsForPublicWorkflowDON(t *testing.T) { wrapper, dispatcher, registry, + &mockDonNotifier{}, ) dispatcher.On("SetReceiver", fullTriggerCapID, capDonID, mock.AnythingOfType("*remote.triggerSubscriber")).Return(nil) @@ -1082,6 +1094,7 @@ func TestLauncher_WiresUpClientsForPublicWorkflowDONButIgnoresPrivateCapabilitie wrapper, dispatcher, registry, + &mockDonNotifier{}, ) dispatcher.On("SetReceiver", fullTriggerCapID, triggerCapDonID, mock.AnythingOfType("*remote.triggerSubscriber")).Return(nil) @@ -1232,6 +1245,7 @@ func TestLauncher_SucceedsEvenIfDispatcherAlreadyHasReceiver(t *testing.T) { wrapper, dispatcher, registry, + &mockDonNotifier{}, ) err = launcher.Launch(ctx, state) diff --git a/core/gethwrappers/ccip/generated/ccip_reader_tester/ccip_reader_tester.go b/core/gethwrappers/ccip/generated/ccip_reader_tester/ccip_reader_tester.go index 168d31806b8..7397594d78d 100644 --- a/core/gethwrappers/ccip/generated/ccip_reader_tester/ccip_reader_tester.go +++ b/core/gethwrappers/ccip/generated/ccip_reader_tester/ccip_reader_tester.go @@ -100,8 +100,8 @@ type OffRampSourceChainConfig struct { } var CCIPReaderTesterMetaData = &bind.MetaData{ - ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"receiver\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"feeToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"feeTokenAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeValueJuels\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"sourcePoolAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"destTokenAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"destExecData\",\"type\":\"bytes\"}],\"internalType\":\"structInternal.EVM2AnyTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structInternal.EVM2AnyRampMessage\",\"name\":\"message\",\"type\":\"tuple\"}],\"name\":\"CCIPMessageSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRampAddress\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"maxSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"indexed\":false,\"internalType\":\"structInternal.MerkleRoot[]\",\"name\":\"merkleRoots\",\"type\":\"tuple[]\"},{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sourceToken\",\"type\":\"address\"},{\"internalType\":\"uint224\",\"name\":\"usdPerToken\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint224\",\"name\":\"usdPerUnitGas\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.GasPriceUpdate[]\",\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structInternal.PriceUpdates\",\"name\":\"priceUpdates\",\"type\":\"tuple\"}],\"name\":\"CommitReportAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasUsed\",\"type\":\"uint256\"}],\"name\":\"ExecutionStateChanged\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"receiver\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"feeToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"feeTokenAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeValueJuels\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"sourcePoolAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"destTokenAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"destExecData\",\"type\":\"bytes\"}],\"internalType\":\"structInternal.EVM2AnyTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.EVM2AnyRampMessage\",\"name\":\"message\",\"type\":\"tuple\"}],\"name\":\"emitCCIPMessageSent\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sourceToken\",\"type\":\"address\"},{\"internalType\":\"uint224\",\"name\":\"usdPerToken\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint224\",\"name\":\"usdPerUnitGas\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.GasPriceUpdate[]\",\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.PriceUpdates\",\"name\":\"priceUpdates\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRampAddress\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"maxSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"structInternal.MerkleRoot[]\",\"name\":\"merkleRoots\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structIRMNRemote.Signature[]\",\"name\":\"rmnSignatures\",\"type\":\"tuple[]\"}],\"internalType\":\"structOffRamp.CommitReport\",\"name\":\"report\",\"type\":\"tuple\"}],\"name\":\"emitCommitReportAccepted\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"},{\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"gasUsed\",\"type\":\"uint256\"}],\"name\":\"emitExecutionStateChanged\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"}],\"name\":\"getExpectedNextSequenceNumber\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"}],\"name\":\"getInboundNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"getSourceChainConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"setDestChainSeqNr\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"testNonce\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"}],\"name\":\"setInboundNonce\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"sourceChainConfig\",\"type\":\"tuple\"}],\"name\":\"setSourceChainConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x608060405234801561001057600080fd5b506118e3806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a35760003560e01c8063c1a5a35511610076578063c92236251161005b578063c92236251461017c578063e83eabba1461018f578063e9d68a8e146101a257600080fd5b8063c1a5a35514610114578063c7c1cba11461016957600080fd5b80634bf78697146100a85780639041be3d146100bd57806393df2867146100ee578063bfc9b78914610101575b600080fd5b6100bb6100b63660046109d1565b6101c2565b005b6100d06100cb366004610b0c565b61021b565b60405167ffffffffffffffff90911681526020015b60405180910390f35b6100bb6100fc366004610b77565b61024b565b6100bb61010f366004610e25565b6102c6565b6100bb610122366004610fb0565b67ffffffffffffffff918216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001691909216179055565b6100bb610177366004610fe3565b610308565b6100d061018a366004611075565b610365565b6100bb61019d3660046110c8565b6103b1565b6101b56101b0366004610b0c565b61049b565b6040516100e591906111e8565b80600001516060015167ffffffffffffffff168267ffffffffffffffff167f192442a2b2adb6a7948f097023cb6b57d29d3a7a5dd33e6666d33c39cc456f328360405161020f919061132e565b60405180910390a35050565b67ffffffffffffffff80821660009081526001602081905260408220549192610245921690611486565b92915050565b67ffffffffffffffff841660009081526002602052604090819020905184919061027890859085906114d5565b908152604051908190036020019020805467ffffffffffffffff929092167fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000090921691909117905550505050565b602081015181516040517f35c02761bcd3ef995c6a601a1981f4ed3934dcbe5041e24e286c89f5531d17e4926102fd9290916115d9565b60405180910390a150565b848667ffffffffffffffff168867ffffffffffffffff167f05665fe9ad095383d018353f4cbcba77e84db27dd215081bbf7cdf9ae6fbe48b8787878760405161035494939291906116b1565b60405180910390a450505050505050565b67ffffffffffffffff8316600090815260026020526040808220905161038e90859085906114d5565b9081526040519081900360200190205467ffffffffffffffff1690509392505050565b67ffffffffffffffff808316600090815260208181526040918290208451815492860151938601519094167501000000000000000000000000000000000000000000027fffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffff93151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff9095169490941791909117919091169190911781556060820151829190600182019061049490826117bc565b5050505050565b604080516080808201835260008083526020808401829052838501829052606080850181905267ffffffffffffffff87811684528383529286902086519485018752805473ffffffffffffffffffffffffffffffffffffffff8116865274010000000000000000000000000000000000000000810460ff16151593860193909352750100000000000000000000000000000000000000000090920490921694830194909452600184018054939492939184019161055790611718565b80601f016020809104026020016040519081016040528092919081815260200182805461058390611718565b80156105d05780601f106105a5576101008083540402835291602001916105d0565b820191906000526020600020905b8154815290600101906020018083116105b357829003601f168201915b5050505050815250509050919050565b803567ffffffffffffffff811681146105f857600080fd5b919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405160a0810167ffffffffffffffff8111828210171561064f5761064f6105fd565b60405290565b604051610120810167ffffffffffffffff8111828210171561064f5761064f6105fd565b6040805190810167ffffffffffffffff8111828210171561064f5761064f6105fd565b6040516060810167ffffffffffffffff8111828210171561064f5761064f6105fd565b6040516080810167ffffffffffffffff8111828210171561064f5761064f6105fd565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715610729576107296105fd565b604052919050565b600060a0828403121561074357600080fd5b61074b61062c565b90508135815261075d602083016105e0565b602082015261076e604083016105e0565b604082015261077f606083016105e0565b6060820152610790608083016105e0565b608082015292915050565b73ffffffffffffffffffffffffffffffffffffffff811681146107bd57600080fd5b50565b80356105f88161079b565b600082601f8301126107dc57600080fd5b813567ffffffffffffffff8111156107f6576107f66105fd565b61082760207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116016106e2565b81815284602083860101111561083c57600080fd5b816020850160208301376000918101602001919091529392505050565b600067ffffffffffffffff821115610873576108736105fd565b5060051b60200190565b600082601f83011261088e57600080fd5b813560206108a361089e83610859565b6106e2565b82815260059290921b840181019181810190868411156108c257600080fd5b8286015b848110156109c657803567ffffffffffffffff808211156108e75760008081fd5b818901915060a0807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0848d030112156109205760008081fd5b61092861062c565b6109338885016107c0565b8152604080850135848111156109495760008081fd5b6109578e8b838901016107cb565b8a84015250606080860135858111156109705760008081fd5b61097e8f8c838a01016107cb565b838501525060809150818601358184015250828501359250838311156109a45760008081fd5b6109b28d8a858801016107cb565b9082015286525050509183019183016108c6565b509695505050505050565b600080604083850312156109e457600080fd5b6109ed836105e0565b9150602083013567ffffffffffffffff80821115610a0a57600080fd5b908401906101a08287031215610a1f57600080fd5b610a27610655565b610a318784610731565b8152610a3f60a084016107c0565b602082015260c083013582811115610a5657600080fd5b610a62888286016107cb565b60408301525060e083013582811115610a7a57600080fd5b610a86888286016107cb565b6060830152506101008084013583811115610aa057600080fd5b610aac898287016107cb565b608084015250610abf61012085016107c0565b60a083015261014084013560c083015261016084013560e083015261018084013583811115610aed57600080fd5b610af98982870161087d565b8284015250508093505050509250929050565b600060208284031215610b1e57600080fd5b610b27826105e0565b9392505050565b60008083601f840112610b4057600080fd5b50813567ffffffffffffffff811115610b5857600080fd5b602083019150836020828501011115610b7057600080fd5b9250929050565b60008060008060608587031215610b8d57600080fd5b610b96856105e0565b9350610ba4602086016105e0565b9250604085013567ffffffffffffffff811115610bc057600080fd5b610bcc87828801610b2e565b95989497509550505050565b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff811681146105f857600080fd5b600082601f830112610c1557600080fd5b81356020610c2561089e83610859565b82815260069290921b84018101918181019086841115610c4457600080fd5b8286015b848110156109c65760408189031215610c615760008081fd5b610c69610679565b610c72826105e0565b8152610c7f858301610bd8565b81860152835291830191604001610c48565b600082601f830112610ca257600080fd5b81356020610cb261089e83610859565b82815260059290921b84018101918181019086841115610cd157600080fd5b8286015b848110156109c657803567ffffffffffffffff80821115610cf65760008081fd5b818901915060a0807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0848d03011215610d2f5760008081fd5b610d3761062c565b610d428885016105e0565b815260408085013584811115610d585760008081fd5b610d668e8b838901016107cb565b8a8401525060609350610d7a8486016105e0565b908201526080610d8b8582016105e0565b93820193909352920135908201528352918301918301610cd5565b600082601f830112610db757600080fd5b81356020610dc761089e83610859565b82815260069290921b84018101918181019086841115610de657600080fd5b8286015b848110156109c65760408189031215610e035760008081fd5b610e0b610679565b813581528482013585820152835291830191604001610dea565b60006020808385031215610e3857600080fd5b823567ffffffffffffffff80821115610e5057600080fd5b9084019060608287031215610e6457600080fd5b610e6c61069c565b823582811115610e7b57600080fd5b83016040818903811315610e8e57600080fd5b610e96610679565b823585811115610ea557600080fd5b8301601f81018b13610eb657600080fd5b8035610ec461089e82610859565b81815260069190911b8201890190898101908d831115610ee357600080fd5b928a01925b82841015610f335785848f031215610f005760008081fd5b610f08610679565b8435610f138161079b565b8152610f20858d01610bd8565b818d0152825292850192908a0190610ee8565b845250505082870135915084821115610f4b57600080fd5b610f578a838501610c04565b81880152835250508284013582811115610f7057600080fd5b610f7c88828601610c91565b85830152506040830135935081841115610f9557600080fd5b610fa187858501610da6565b60408201529695505050505050565b60008060408385031215610fc357600080fd5b610fcc836105e0565b9150610fda602084016105e0565b90509250929050565b600080600080600080600060e0888a031215610ffe57600080fd5b611007886105e0565b9650611015602089016105e0565b9550604088013594506060880135935060808801356004811061103757600080fd5b925060a088013567ffffffffffffffff81111561105357600080fd5b61105f8a828b016107cb565b92505060c0880135905092959891949750929550565b60008060006040848603121561108a57600080fd5b611093846105e0565b9250602084013567ffffffffffffffff8111156110af57600080fd5b6110bb86828701610b2e565b9497909650939450505050565b600080604083850312156110db57600080fd5b6110e4836105e0565b9150602083013567ffffffffffffffff8082111561110157600080fd5b908401906080828703121561111557600080fd5b61111d6106bf565b82356111288161079b565b81526020830135801515811461113d57600080fd5b602082015261114e604084016105e0565b604082015260608301358281111561116557600080fd5b611171888286016107cb565b6060830152508093505050509250929050565b6000815180845260005b818110156111aa5760208185018101518683018201520161118e565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815273ffffffffffffffffffffffffffffffffffffffff825116602082015260208201511515604082015267ffffffffffffffff60408301511660608201526000606083015160808084015261124360a0840182611184565b949350505050565b600082825180855260208086019550808260051b84010181860160005b84811015611321577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0868403018952815160a073ffffffffffffffffffffffffffffffffffffffff82511685528582015181878701526112ca82870182611184565b915050604080830151868303828801526112e48382611184565b9250505060608083015181870152506080808301519250858203818701525061130d8183611184565b9a86019a9450505090830190600101611268565b5090979650505050505050565b6020815261137f60208201835180518252602081015167ffffffffffffffff808216602085015280604084015116604085015280606084015116606085015280608084015116608085015250505050565b600060208301516113a860c084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060408301516101a08060e08501526113c56101c0850183611184565b915060608501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06101008187860301818801526114038584611184565b94506080880151925081878603016101208801526114218584611184565b945060a0880151925061144d61014088018473ffffffffffffffffffffffffffffffffffffffff169052565b60c088015161016088015260e088015161018088015287015186850390910183870152905061147c838261124b565b9695505050505050565b67ffffffffffffffff8181168382160190808211156114ce577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5092915050565b8183823760009101908152919050565b805160408084528151848201819052600092602091908201906060870190855b8181101561155e578351805173ffffffffffffffffffffffffffffffffffffffff1684528501517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16858401529284019291850191600101611505565b50508583015187820388850152805180835290840192506000918401905b808310156115cd578351805167ffffffffffffffff1683528501517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff168583015292840192600192909201919085019061157c565b50979650505050505050565b60006040808301604084528086518083526060925060608601915060608160051b8701016020808a0160005b84811015611691577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08a8503018652815160a067ffffffffffffffff80835116875285830151828789015261165c83890182611184565b848d01518316898e01528b8501519092168b890152506080928301519290960191909152509482019490820190600101611605565b5050878203908801526116a481896114e5565b9998505050505050505050565b8481526000600485106116ed577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b846020830152608060408301526117076080830185611184565b905082606083015295945050505050565b600181811c9082168061172c57607f821691505b602082108103611765577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f8211156117b7576000816000526020600020601f850160051c810160208610156117945750805b601f850160051c820191505b818110156117b3578281556001016117a0565b5050505b505050565b815167ffffffffffffffff8111156117d6576117d66105fd565b6117ea816117e48454611718565b8461176b565b602080601f83116001811461183d57600084156118075750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b1785556117b3565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561188a5788860151825594840194600190910190840161186b565b50858210156118c657878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b0190555056fea164736f6c6343000818000a", + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"receiver\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"feeToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"feeTokenAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeValueJuels\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"sourcePoolAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"destTokenAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"destExecData\",\"type\":\"bytes\"}],\"internalType\":\"structInternal.EVM2AnyTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structInternal.EVM2AnyRampMessage\",\"name\":\"message\",\"type\":\"tuple\"}],\"name\":\"CCIPMessageSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRampAddress\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"maxSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"indexed\":false,\"internalType\":\"structInternal.MerkleRoot[]\",\"name\":\"merkleRoots\",\"type\":\"tuple[]\"},{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sourceToken\",\"type\":\"address\"},{\"internalType\":\"uint224\",\"name\":\"usdPerToken\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint224\",\"name\":\"usdPerUnitGas\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.GasPriceUpdate[]\",\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structInternal.PriceUpdates\",\"name\":\"priceUpdates\",\"type\":\"tuple\"}],\"name\":\"CommitReportAccepted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasUsed\",\"type\":\"uint256\"}],\"name\":\"ExecutionStateChanged\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structInternal.RampMessageHeader\",\"name\":\"header\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"receiver\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraArgs\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"feeToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"feeTokenAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeValueJuels\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"sourcePoolAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"destTokenAddress\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"destExecData\",\"type\":\"bytes\"}],\"internalType\":\"structInternal.EVM2AnyTokenTransfer[]\",\"name\":\"tokenAmounts\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.EVM2AnyRampMessage\",\"name\":\"message\",\"type\":\"tuple\"}],\"name\":\"emitCCIPMessageSent\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sourceToken\",\"type\":\"address\"},{\"internalType\":\"uint224\",\"name\":\"usdPerToken\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.TokenPriceUpdate[]\",\"name\":\"tokenPriceUpdates\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint224\",\"name\":\"usdPerUnitGas\",\"type\":\"uint224\"}],\"internalType\":\"structInternal.GasPriceUpdate[]\",\"name\":\"gasPriceUpdates\",\"type\":\"tuple[]\"}],\"internalType\":\"structInternal.PriceUpdates\",\"name\":\"priceUpdates\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRampAddress\",\"type\":\"bytes\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"maxSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"merkleRoot\",\"type\":\"bytes32\"}],\"internalType\":\"structInternal.MerkleRoot[]\",\"name\":\"merkleRoots\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structIRMNRemote.Signature[]\",\"name\":\"rmnSignatures\",\"type\":\"tuple[]\"}],\"internalType\":\"structOffRamp.CommitReport\",\"name\":\"report\",\"type\":\"tuple\"}],\"name\":\"emitCommitReportAccepted\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"messageId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"messageHash\",\"type\":\"bytes32\"},{\"internalType\":\"enumInternal.MessageExecutionState\",\"name\":\"state\",\"type\":\"uint8\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"gasUsed\",\"type\":\"uint256\"}],\"name\":\"emitExecutionStateChanged\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"}],\"name\":\"getExpectedNextSequenceNumber\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"}],\"name\":\"getInboundNonce\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLatestPriceSequenceNumber\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"}],\"name\":\"getSourceChainConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"destChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"sequenceNumber\",\"type\":\"uint64\"}],\"name\":\"setDestChainSeqNr\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"testNonce\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"sender\",\"type\":\"bytes\"}],\"name\":\"setInboundNonce\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"seqNr\",\"type\":\"uint64\"}],\"name\":\"setLatestPriceSequenceNumber\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"sourceChainSelector\",\"type\":\"uint64\"},{\"components\":[{\"internalType\":\"contractIRouter\",\"name\":\"router\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint64\",\"name\":\"minSeqNr\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"onRamp\",\"type\":\"bytes\"}],\"internalType\":\"structOffRamp.SourceChainConfig\",\"name\":\"sourceChainConfig\",\"type\":\"tuple\"}],\"name\":\"setSourceChainConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50611960806100206000396000f3fe608060405234801561001057600080fd5b50600436106100c95760003560e01c8063bfc9b78911610081578063c92236251161005b578063c9223625146101f9578063e83eabba1461020c578063e9d68a8e1461021f57600080fd5b8063bfc9b7891461017e578063c1a5a35514610191578063c7c1cba1146101e657600080fd5b806369600bca116100b257806369600bca1461010f5780639041be3d1461015857806393df28671461016b57600080fd5b80633f4b04aa146100ce5780634bf78697146100fa575b600080fd5b60035467ffffffffffffffff165b60405167ffffffffffffffff90911681526020015b60405180910390f35b61010d610108366004610a4e565b61023f565b005b61010d61011d366004610b89565b600380547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff92909216919091179055565b6100dc610166366004610b89565b610298565b61010d610179366004610bf4565b6102c8565b61010d61018c366004610ea2565b610343565b61010d61019f36600461102d565b67ffffffffffffffff918216600090815260016020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001691909216179055565b61010d6101f4366004611060565b610385565b6100dc6102073660046110f2565b6103e2565b61010d61021a366004611145565b61042e565b61023261022d366004610b89565b610518565b6040516100f19190611265565b80600001516060015167ffffffffffffffff168267ffffffffffffffff167f192442a2b2adb6a7948f097023cb6b57d29d3a7a5dd33e6666d33c39cc456f328360405161028c91906113ab565b60405180910390a35050565b67ffffffffffffffff808216600090815260016020819052604082205491926102c2921690611503565b92915050565b67ffffffffffffffff84166000908152600260205260409081902090518491906102f59085908590611552565b908152604051908190036020019020805467ffffffffffffffff929092167fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000090921691909117905550505050565b602081015181516040517f35c02761bcd3ef995c6a601a1981f4ed3934dcbe5041e24e286c89f5531d17e49261037a929091611656565b60405180910390a150565b848667ffffffffffffffff168867ffffffffffffffff167f05665fe9ad095383d018353f4cbcba77e84db27dd215081bbf7cdf9ae6fbe48b878787876040516103d1949392919061172e565b60405180910390a450505050505050565b67ffffffffffffffff8316600090815260026020526040808220905161040b9085908590611552565b9081526040519081900360200190205467ffffffffffffffff1690509392505050565b67ffffffffffffffff808316600090815260208181526040918290208451815492860151938601519094167501000000000000000000000000000000000000000000027fffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffff93151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00000000000000000000000000000000000000000090931673ffffffffffffffffffffffffffffffffffffffff909516949094179190911791909116919091178155606082015182919060018201906105119082611839565b5050505050565b604080516080808201835260008083526020808401829052838501829052606080850181905267ffffffffffffffff87811684528383529286902086519485018752805473ffffffffffffffffffffffffffffffffffffffff8116865274010000000000000000000000000000000000000000810460ff1615159386019390935275010000000000000000000000000000000000000000009092049092169483019490945260018401805493949293918401916105d490611795565b80601f016020809104026020016040519081016040528092919081815260200182805461060090611795565b801561064d5780601f106106225761010080835404028352916020019161064d565b820191906000526020600020905b81548152906001019060200180831161063057829003601f168201915b5050505050815250509050919050565b803567ffffffffffffffff8116811461067557600080fd5b919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405160a0810167ffffffffffffffff811182821017156106cc576106cc61067a565b60405290565b604051610120810167ffffffffffffffff811182821017156106cc576106cc61067a565b6040805190810167ffffffffffffffff811182821017156106cc576106cc61067a565b6040516060810167ffffffffffffffff811182821017156106cc576106cc61067a565b6040516080810167ffffffffffffffff811182821017156106cc576106cc61067a565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156107a6576107a661067a565b604052919050565b600060a082840312156107c057600080fd5b6107c86106a9565b9050813581526107da6020830161065d565b60208201526107eb6040830161065d565b60408201526107fc6060830161065d565b606082015261080d6080830161065d565b608082015292915050565b73ffffffffffffffffffffffffffffffffffffffff8116811461083a57600080fd5b50565b803561067581610818565b600082601f83011261085957600080fd5b813567ffffffffffffffff8111156108735761087361067a565b6108a460207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161075f565b8181528460208386010111156108b957600080fd5b816020850160208301376000918101602001919091529392505050565b600067ffffffffffffffff8211156108f0576108f061067a565b5060051b60200190565b600082601f83011261090b57600080fd5b8135602061092061091b836108d6565b61075f565b82815260059290921b8401810191818101908684111561093f57600080fd5b8286015b84811015610a4357803567ffffffffffffffff808211156109645760008081fd5b818901915060a0807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0848d0301121561099d5760008081fd5b6109a56106a9565b6109b088850161083d565b8152604080850135848111156109c65760008081fd5b6109d48e8b83890101610848565b8a84015250606080860135858111156109ed5760008081fd5b6109fb8f8c838a0101610848565b83850152506080915081860135818401525082850135925083831115610a215760008081fd5b610a2f8d8a85880101610848565b908201528652505050918301918301610943565b509695505050505050565b60008060408385031215610a6157600080fd5b610a6a8361065d565b9150602083013567ffffffffffffffff80821115610a8757600080fd5b908401906101a08287031215610a9c57600080fd5b610aa46106d2565b610aae87846107ae565b8152610abc60a0840161083d565b602082015260c083013582811115610ad357600080fd5b610adf88828601610848565b60408301525060e083013582811115610af757600080fd5b610b0388828601610848565b6060830152506101008084013583811115610b1d57600080fd5b610b2989828701610848565b608084015250610b3c610120850161083d565b60a083015261014084013560c083015261016084013560e083015261018084013583811115610b6a57600080fd5b610b76898287016108fa565b8284015250508093505050509250929050565b600060208284031215610b9b57600080fd5b610ba48261065d565b9392505050565b60008083601f840112610bbd57600080fd5b50813567ffffffffffffffff811115610bd557600080fd5b602083019150836020828501011115610bed57600080fd5b9250929050565b60008060008060608587031215610c0a57600080fd5b610c138561065d565b9350610c216020860161065d565b9250604085013567ffffffffffffffff811115610c3d57600080fd5b610c4987828801610bab565b95989497509550505050565b80357bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8116811461067557600080fd5b600082601f830112610c9257600080fd5b81356020610ca261091b836108d6565b82815260069290921b84018101918181019086841115610cc157600080fd5b8286015b84811015610a435760408189031215610cde5760008081fd5b610ce66106f6565b610cef8261065d565b8152610cfc858301610c55565b81860152835291830191604001610cc5565b600082601f830112610d1f57600080fd5b81356020610d2f61091b836108d6565b82815260059290921b84018101918181019086841115610d4e57600080fd5b8286015b84811015610a4357803567ffffffffffffffff80821115610d735760008081fd5b818901915060a0807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0848d03011215610dac5760008081fd5b610db46106a9565b610dbf88850161065d565b815260408085013584811115610dd55760008081fd5b610de38e8b83890101610848565b8a8401525060609350610df784860161065d565b908201526080610e0885820161065d565b93820193909352920135908201528352918301918301610d52565b600082601f830112610e3457600080fd5b81356020610e4461091b836108d6565b82815260069290921b84018101918181019086841115610e6357600080fd5b8286015b84811015610a435760408189031215610e805760008081fd5b610e886106f6565b813581528482013585820152835291830191604001610e67565b60006020808385031215610eb557600080fd5b823567ffffffffffffffff80821115610ecd57600080fd5b9084019060608287031215610ee157600080fd5b610ee9610719565b823582811115610ef857600080fd5b83016040818903811315610f0b57600080fd5b610f136106f6565b823585811115610f2257600080fd5b8301601f81018b13610f3357600080fd5b8035610f4161091b826108d6565b81815260069190911b8201890190898101908d831115610f6057600080fd5b928a01925b82841015610fb05785848f031215610f7d5760008081fd5b610f856106f6565b8435610f9081610818565b8152610f9d858d01610c55565b818d0152825292850192908a0190610f65565b845250505082870135915084821115610fc857600080fd5b610fd48a838501610c81565b81880152835250508284013582811115610fed57600080fd5b610ff988828601610d0e565b8583015250604083013593508184111561101257600080fd5b61101e87858501610e23565b60408201529695505050505050565b6000806040838503121561104057600080fd5b6110498361065d565b91506110576020840161065d565b90509250929050565b600080600080600080600060e0888a03121561107b57600080fd5b6110848861065d565b96506110926020890161065d565b955060408801359450606088013593506080880135600481106110b457600080fd5b925060a088013567ffffffffffffffff8111156110d057600080fd5b6110dc8a828b01610848565b92505060c0880135905092959891949750929550565b60008060006040848603121561110757600080fd5b6111108461065d565b9250602084013567ffffffffffffffff81111561112c57600080fd5b61113886828701610bab565b9497909650939450505050565b6000806040838503121561115857600080fd5b6111618361065d565b9150602083013567ffffffffffffffff8082111561117e57600080fd5b908401906080828703121561119257600080fd5b61119a61073c565b82356111a581610818565b8152602083013580151581146111ba57600080fd5b60208201526111cb6040840161065d565b60408201526060830135828111156111e257600080fd5b6111ee88828601610848565b6060830152508093505050509250929050565b6000815180845260005b818110156112275760208185018101518683018201520161120b565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b6020815273ffffffffffffffffffffffffffffffffffffffff825116602082015260208201511515604082015267ffffffffffffffff6040830151166060820152600060608301516080808401526112c060a0840182611201565b949350505050565b600082825180855260208086019550808260051b84010181860160005b8481101561139e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0868403018952815160a073ffffffffffffffffffffffffffffffffffffffff825116855285820151818787015261134782870182611201565b915050604080830151868303828801526113618382611201565b9250505060608083015181870152506080808301519250858203818701525061138a8183611201565b9a86019a94505050908301906001016112e5565b5090979650505050505050565b602081526113fc60208201835180518252602081015167ffffffffffffffff808216602085015280604084015116604085015280606084015116606085015280608084015116608085015250505050565b6000602083015161142560c084018273ffffffffffffffffffffffffffffffffffffffff169052565b5060408301516101a08060e08501526114426101c0850183611201565b915060608501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06101008187860301818801526114808584611201565b945060808801519250818786030161012088015261149e8584611201565b945060a088015192506114ca61014088018473ffffffffffffffffffffffffffffffffffffffff169052565b60c088015161016088015260e08801516101808801528701518685039091018387015290506114f983826112c8565b9695505050505050565b67ffffffffffffffff81811683821601908082111561154b577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5092915050565b8183823760009101908152919050565b805160408084528151848201819052600092602091908201906060870190855b818110156115db578351805173ffffffffffffffffffffffffffffffffffffffff1684528501517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16858401529284019291850191600101611582565b50508583015187820388850152805180835290840192506000918401905b8083101561164a578351805167ffffffffffffffff1683528501517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff16858301529284019260019290920191908501906115f9565b50979650505050505050565b60006040808301604084528086518083526060925060608601915060608160051b8701016020808a0160005b8481101561170e577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08a8503018652815160a067ffffffffffffffff8083511687528583015182878901526116d983890182611201565b848d01518316898e01528b8501519092168b890152506080928301519290960191909152509482019490820190600101611682565b5050878203908801526117218189611562565b9998505050505050505050565b84815260006004851061176a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b846020830152608060408301526117846080830185611201565b905082606083015295945050505050565b600181811c908216806117a957607f821691505b6020821081036117e2577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f821115611834576000816000526020600020601f850160051c810160208610156118115750805b601f850160051c820191505b818110156118305782815560010161181d565b5050505b505050565b815167ffffffffffffffff8111156118535761185361067a565b611867816118618454611795565b846117e8565b602080601f8311600181146118ba57600084156118845750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555611830565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b82811015611907578886015182559484019460019091019084016118e8565b508582101561194357878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b0190555056fea164736f6c6343000818000a", } var CCIPReaderTesterABI = CCIPReaderTesterMetaData.ABI @@ -284,6 +284,28 @@ func (_CCIPReaderTester *CCIPReaderTesterCallerSession) GetInboundNonce(sourceCh return _CCIPReaderTester.Contract.GetInboundNonce(&_CCIPReaderTester.CallOpts, sourceChainSelector, sender) } +func (_CCIPReaderTester *CCIPReaderTesterCaller) GetLatestPriceSequenceNumber(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _CCIPReaderTester.contract.Call(opts, &out, "getLatestPriceSequenceNumber") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +func (_CCIPReaderTester *CCIPReaderTesterSession) GetLatestPriceSequenceNumber() (uint64, error) { + return _CCIPReaderTester.Contract.GetLatestPriceSequenceNumber(&_CCIPReaderTester.CallOpts) +} + +func (_CCIPReaderTester *CCIPReaderTesterCallerSession) GetLatestPriceSequenceNumber() (uint64, error) { + return _CCIPReaderTester.Contract.GetLatestPriceSequenceNumber(&_CCIPReaderTester.CallOpts) +} + func (_CCIPReaderTester *CCIPReaderTesterCaller) GetSourceChainConfig(opts *bind.CallOpts, sourceChainSelector uint64) (OffRampSourceChainConfig, error) { var out []interface{} err := _CCIPReaderTester.contract.Call(opts, &out, "getSourceChainConfig", sourceChainSelector) @@ -366,6 +388,18 @@ func (_CCIPReaderTester *CCIPReaderTesterTransactorSession) SetInboundNonce(sour return _CCIPReaderTester.Contract.SetInboundNonce(&_CCIPReaderTester.TransactOpts, sourceChainSelector, testNonce, sender) } +func (_CCIPReaderTester *CCIPReaderTesterTransactor) SetLatestPriceSequenceNumber(opts *bind.TransactOpts, seqNr uint64) (*types.Transaction, error) { + return _CCIPReaderTester.contract.Transact(opts, "setLatestPriceSequenceNumber", seqNr) +} + +func (_CCIPReaderTester *CCIPReaderTesterSession) SetLatestPriceSequenceNumber(seqNr uint64) (*types.Transaction, error) { + return _CCIPReaderTester.Contract.SetLatestPriceSequenceNumber(&_CCIPReaderTester.TransactOpts, seqNr) +} + +func (_CCIPReaderTester *CCIPReaderTesterTransactorSession) SetLatestPriceSequenceNumber(seqNr uint64) (*types.Transaction, error) { + return _CCIPReaderTester.Contract.SetLatestPriceSequenceNumber(&_CCIPReaderTester.TransactOpts, seqNr) +} + func (_CCIPReaderTester *CCIPReaderTesterTransactor) SetSourceChainConfig(opts *bind.TransactOpts, sourceChainSelector uint64, sourceChainConfig OffRampSourceChainConfig) (*types.Transaction, error) { return _CCIPReaderTester.contract.Transact(opts, "setSourceChainConfig", sourceChainSelector, sourceChainConfig) } @@ -817,6 +851,8 @@ type CCIPReaderTesterInterface interface { GetInboundNonce(opts *bind.CallOpts, sourceChainSelector uint64, sender []byte) (uint64, error) + GetLatestPriceSequenceNumber(opts *bind.CallOpts) (uint64, error) + GetSourceChainConfig(opts *bind.CallOpts, sourceChainSelector uint64) (OffRampSourceChainConfig, error) EmitCCIPMessageSent(opts *bind.TransactOpts, destChainSelector uint64, message InternalEVM2AnyRampMessage) (*types.Transaction, error) @@ -829,6 +865,8 @@ type CCIPReaderTesterInterface interface { SetInboundNonce(opts *bind.TransactOpts, sourceChainSelector uint64, testNonce uint64, sender []byte) (*types.Transaction, error) + SetLatestPriceSequenceNumber(opts *bind.TransactOpts, seqNr uint64) (*types.Transaction, error) + SetSourceChainConfig(opts *bind.TransactOpts, sourceChainSelector uint64, sourceChainConfig OffRampSourceChainConfig) (*types.Transaction, error) FilterCCIPMessageSent(opts *bind.FilterOpts, destChainSelector []uint64, sequenceNumber []uint64) (*CCIPReaderTesterCCIPMessageSentIterator, error) 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 331f9400d5a..84a8132708c 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 @@ -4,7 +4,7 @@ burn_mint_token_pool: ../../../contracts/solc/v0.8.24/BurnMintTokenPool/BurnMint burn_with_from_mint_token_pool: ../../../contracts/solc/v0.8.24/BurnWithFromMintTokenPool/BurnWithFromMintTokenPool.abi ../../../contracts/solc/v0.8.24/BurnWithFromMintTokenPool/BurnWithFromMintTokenPool.bin c3f723e7f6394297c095a9d9696f1bceec4a2e85b5be2159f7a21d257eb6b480 ccip_encoding_utils: ../../../contracts/solc/v0.8.24/ICCIPEncodingUtils/ICCIPEncodingUtils.abi ../../../contracts/solc/v0.8.24/ICCIPEncodingUtils/ICCIPEncodingUtils.bin 9971fc93c34442a0989570d3dab90a125de31e6e60754ad972807ce6ad4dfba0 ccip_home: ../../../contracts/solc/v0.8.24/CCIPHome/CCIPHome.abi ../../../contracts/solc/v0.8.24/CCIPHome/CCIPHome.bin 02cb75b4274a5be7f4006cf2b72cc09e77eb6dba4c1a9c720af86668ff8ea1df -ccip_reader_tester: ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.abi ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.bin 893c9930e874fe5235db24e28a22650c37f562da94fac93618566bcd84839fdc +ccip_reader_tester: ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.abi ../../../contracts/solc/v0.8.24/CCIPReaderTester/CCIPReaderTester.bin b368699ae7dbee7c21d049a641642837f18ce2cc8d4ece69509f205de673108e ether_sender_receiver: ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.abi ../../../contracts/solc/v0.8.24/EtherSenderReceiver/EtherSenderReceiver.bin 09510a3f773f108a3c231e8d202835c845ded862d071ec54c4f89c12d868b8de fee_quoter: ../../../contracts/solc/v0.8.24/FeeQuoter/FeeQuoter.abi ../../../contracts/solc/v0.8.24/FeeQuoter/FeeQuoter.bin 8a0869d14bb5247fbc6d836fc20d123358373ed688e0d3b387d59e7d05496fea lock_release_token_pool: ../../../contracts/solc/v0.8.24/LockReleaseTokenPool/LockReleaseTokenPool.abi ../../../contracts/solc/v0.8.24/LockReleaseTokenPool/LockReleaseTokenPool.bin 1067f557abeb5570f1da7f050ea982ffad0f35dc064e668a8a0e6af128df490c diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 1cc5d996f98..1c19beceff3 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -297,7 +297,7 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 // indirect github.com/smartcontractkit/chain-selectors v1.0.31 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29ea018b57 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index bffd9984b88..2753d3c6b51 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1092,8 +1092,8 @@ github.com/smartcontractkit/chain-selectors v1.0.31 h1:oRHyK88KnsCh4OdU2hr0u70pm github.com/smartcontractkit/chain-selectors v1.0.31/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-20241125151847-c63f5f567fcd h1:hzisF429DPXIXg2yXOHT1Z0TeUcJSO71WN1u03yoeMU= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 h1:A/qi1YCY/9V9i/sthhizZCA0EECAcBfDKeA2w27H5fo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d h1:0tnjo1gpG16PHAouXamgDAAu6e7PWaM0Ppq6dMWnjx0= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 01f5d8b530a..68b9b99a823 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -20,6 +20,7 @@ import ( "go.uber.org/multierr" "go.uber.org/zap/zapcore" + "github.com/smartcontractkit/chainlink-common/pkg/custmsg" "github.com/smartcontractkit/chainlink-common/pkg/loop" commonservices "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" @@ -33,6 +34,7 @@ import ( gatewayconnector "github.com/smartcontractkit/chainlink/v2/core/capabilities/gateway_connector" "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote" remotetypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/remote/types" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/webapi" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -48,6 +50,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/feeds" "github.com/smartcontractkit/chainlink/v2/core/services/fluxmonitorv2" "github.com/smartcontractkit/chainlink/v2/core/services/gateway" + capabilities2 "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/capabilities" + common2 "github.com/smartcontractkit/chainlink/v2/core/services/gateway/handlers/common" "github.com/smartcontractkit/chainlink/v2/core/services/headreporter" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/services/keeper" @@ -71,6 +75,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/webhook" "github.com/smartcontractkit/chainlink/v2/core/services/workflows" workflowstore "github.com/smartcontractkit/chainlink/v2/core/services/workflows/store" + "github.com/smartcontractkit/chainlink/v2/core/services/workflows/syncer" "github.com/smartcontractkit/chainlink/v2/core/sessions" "github.com/smartcontractkit/chainlink/v2/core/sessions/ldapauth" "github.com/smartcontractkit/chainlink/v2/core/sessions/localauth" @@ -212,6 +217,17 @@ func NewApplication(opts ApplicationOpts) (Application, error) { opts.CapabilitiesRegistry = capabilities.NewRegistry(globalLogger) } + var gatewayConnectorWrapper *gatewayconnector.ServiceWrapper + if cfg.Capabilities().GatewayConnector().DonID() != "" { + globalLogger.Debugw("Creating GatewayConnector wrapper", "donID", cfg.Capabilities().GatewayConnector().DonID()) + gatewayConnectorWrapper = gatewayconnector.NewGatewayConnectorServiceWrapper( + cfg.Capabilities().GatewayConnector(), + keyStore.Eth(), + clockwork.NewRealClock(), + globalLogger) + srvcs = append(srvcs, gatewayConnectorWrapper) + } + var externalPeerWrapper p2ptypes.PeerWrapper if cfg.Capabilities().Peering().Enabled() { var dispatcher remotetypes.Dispatcher @@ -256,32 +272,79 @@ func NewApplication(opts ApplicationOpts) (Application, error) { return nil, fmt.Errorf("could not configure syncer: %w", err) } + workflowDonNotifier := capabilities.NewDonNotifier() + wfLauncher := capabilities.NewLauncher( globalLogger, externalPeerWrapper, dispatcher, opts.CapabilitiesRegistry, + workflowDonNotifier, ) registrySyncer.AddLauncher(wfLauncher) srvcs = append(srvcs, wfLauncher, registrySyncer) + + if cfg.Capabilities().WorkflowRegistry().Address() != "" { + if gatewayConnectorWrapper == nil { + return nil, errors.New("unable to create workflow registry syncer without gateway connector") + } + + err = keyStore.Workflow().EnsureKey(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to ensure workflow key: %w", err) + } + + keys, err := keyStore.Workflow().GetAll() + if err != nil { + return nil, fmt.Errorf("failed to get all workflow keys: %w", err) + } + if len(keys) != 1 { + return nil, fmt.Errorf("expected 1 key, got %d", len(keys)) + } + + connector := gatewayConnectorWrapper.GetGatewayConnector() + webAPILggr := globalLogger.Named("WebAPITarget") + + webAPIConfig := webapi.ServiceConfig{ + RateLimiter: common2.RateLimiterConfig{ + GlobalRPS: 100.0, + GlobalBurst: 100, + PerSenderRPS: 100.0, + PerSenderBurst: 100, + }, + } + + outgoingConnectorHandler, err := webapi.NewOutgoingConnectorHandler(connector, + webAPIConfig, + capabilities2.MethodWebAPITarget, webAPILggr) + if err != nil { + return nil, fmt.Errorf("could not create outgoing connector handler: %w", err) + } + + eventHandler := syncer.NewEventHandler(globalLogger, syncer.NewWorkflowRegistryDS(opts.DS, globalLogger), + syncer.NewFetcherFunc(globalLogger, outgoingConnectorHandler), workflowstore.NewDBStore(opts.DS, globalLogger, clockwork.NewRealClock()), opts.CapabilitiesRegistry, + custmsg.NewLabeler(), clockwork.NewRealClock(), keys[0]) + + loader := syncer.NewWorkflowRegistryContractLoader(cfg.Capabilities().WorkflowRegistry().Address(), func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return relayer.NewContractReader(ctx, bytes) + }, eventHandler) + + wfSyncer := syncer.NewWorkflowRegistry(globalLogger, func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return relayer.NewContractReader(ctx, bytes) + }, cfg.Capabilities().WorkflowRegistry().Address(), + syncer.WorkflowEventPollerConfig{ + QueryCount: 100, + }, eventHandler, loader, workflowDonNotifier) + + srvcs = append(srvcs, wfSyncer) + } } } else { globalLogger.Debug("External registry not configured, skipping registry syncer and starting with an empty registry") opts.CapabilitiesRegistry.SetLocalRegistry(&capabilities.TestMetadataRegistry{}) } - var gatewayConnectorWrapper *gatewayconnector.ServiceWrapper - if cfg.Capabilities().GatewayConnector().DonID() != "" { - globalLogger.Debugw("Creating GatewayConnector wrapper", "donID", cfg.Capabilities().GatewayConnector().DonID()) - gatewayConnectorWrapper = gatewayconnector.NewGatewayConnectorServiceWrapper( - cfg.Capabilities().GatewayConnector(), - keyStore.Eth(), - clockwork.NewRealClock(), - globalLogger) - srvcs = append(srvcs, gatewayConnectorWrapper) - } - // LOOPs can be created as options, in the case of LOOP relayers, or // as OCR2 job implementations, in the case of Median today. // We will have a non-nil registry here in LOOP relayers are being used, otherwise diff --git a/core/services/feeds/service.go b/core/services/feeds/service.go index ac870f91da6..288ac022c20 100644 --- a/core/services/feeds/service.go +++ b/core/services/feeds/service.go @@ -19,9 +19,11 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" ccip "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/validate" + "github.com/smartcontractkit/chainlink/v2/core/services/streams" "github.com/smartcontractkit/chainlink/v2/plugins" pb "github.com/smartcontractkit/chainlink-protos/orchestrator/feedsmanager" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" @@ -863,6 +865,13 @@ func (s *service) ApproveSpec(ctx context.Context, id int64, force bool) error { if txerr != nil && !errors.Is(txerr, sql.ErrNoRows) { return fmt.Errorf("failed while checking for existing ccip job: %w", txerr) } + case job.Stream: + existingJobID, txerr = tx.jobORM.FindJobIDByStreamID(ctx, *j.StreamID) + // Return an error if the repository errors. If there is a not found + // error we want to continue with approving the job. + if txerr != nil && !errors.Is(txerr, sql.ErrNoRows) { + return fmt.Errorf("failed while checking for existing stream job: %w", txerr) + } default: return errors.Errorf("unsupported job type when approving job proposal specs: %s", j.Type) } @@ -1267,6 +1276,8 @@ func (s *service) generateJob(ctx context.Context, spec string) (*job.Job, error js, err = workflows.ValidatedWorkflowJobSpec(ctx, spec) case job.CCIP: js, err = ccip.ValidatedCCIPSpec(spec) + case job.Stream: + js, err = streams.ValidatedStreamSpec(spec) default: return nil, errors.Errorf("unknown job type: %s", jobType) } diff --git a/core/services/feeds/service_test.go b/core/services/feeds/service_test.go index 1356f19626d..b2510ef8de5 100644 --- a/core/services/feeds/service_test.go +++ b/core/services/feeds/service_test.go @@ -21,6 +21,7 @@ import ( commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" proto "github.com/smartcontractkit/chainlink-protos/orchestrator/feedsmanager" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" evmbig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" @@ -136,6 +137,41 @@ answer1 [type=median index=0]; [pluginConfig.juelsPerFeeCoinCache] updateInterval = "1m" ` + +const StreamTestSpecTemplate = ` +name = '%s' +type = 'stream' +schemaVersion = 1 +externalJobID = '%s' +streamID = %d +observationSource = """ +ds1_payload [type=bridge name=\"bridge-ncfx\" timeout=\"50s\" requestData=\"{\\\"data\\\":{\\\"endpoint\\\":\\\"cryptolwba\\\",\\\"from\\\":\\\"SEI\\\",\\\"to\\\":\\\"USD\\\"}}\"]; +ds1_benchmark [type=jsonparse path=\"data,mid\"]; +ds1_bid [type=jsonparse path=\"data,bid\"]; +ds1_ask [type=jsonparse path=\"data,ask\"]; +ds2_payload [type=bridge name=\"bridge-tiingo\" timeout=\"50s\" requestData=\"{\\\"data\\\":{\\\"endpoint\\\":\\\"cryptolwba\\\",\\\"from\\\":\\\"SEI\\\",\\\"to\\\":\\\"USD\\\"}}\"]; +ds2_benchmark [type=jsonparse path=\"data,mid\"]; +ds2_bid [type=jsonparse path=\"data,bid\"]; +ds2_ask [type=jsonparse path=\"data,ask\"]; +ds3_payload [type=bridge name=\"bridge-gsr\" timeout=\"50s\" requestData=\"{\\\"data\\\":{\\\"endpoint\\\":\\\"cryptolwba\\\",\\\"from\\\":\\\"SEI\\\",\\\"to\\\":\\\"USD\\\"}}\"]; +ds3_benchmark [type=jsonparse path=\"data,mid\"]; +ds3_bid [type=jsonparse path=\"data,bid\"]; +ds3_ask [type=jsonparse path=\"data,ask\"]; +ds1_payload -> ds1_benchmark -> benchmark_price; +ds2_payload -> ds2_benchmark -> benchmark_price; +ds3_payload -> ds3_benchmark -> benchmark_price; +benchmark_price [type=median allowedFaults=2 index=0]; +ds1_payload -> ds1_bid -> bid_price; +ds2_payload -> ds2_bid -> bid_price; +ds3_payload -> ds3_bid -> bid_price; +bid_price [type=median allowedFaults=2 index=1]; +ds1_payload -> ds1_ask -> ask_price; +ds2_payload -> ds2_ask -> ask_price; +ds3_payload -> ds3_ask -> ask_price; +ask_price [type=median allowedFaults=2 index=2]; +""" +` + const BootstrapTestSpecTemplate = ` type = "bootstrap" schemaVersion = 1 @@ -3298,6 +3334,514 @@ updateInterval = "20m" } } +func Test_Service_ApproveSpec_Stream(t *testing.T) { + externalJobID := uuid.New() + streamName := "LINK / ETH | version 3 | contract 0x0000000000000000000000000000000000000000" + streamID := uint32(1009001032) + + var ( + ctx = testutils.Context(t) + + jp = &feeds.JobProposal{ + ID: 1, + FeedsManagerID: 100, + } + spec = &feeds.JobProposalSpec{ + ID: 20, + Status: feeds.SpecStatusPending, + JobProposalID: jp.ID, + Version: 1, + Definition: fmt.Sprintf(StreamTestSpecTemplate, streamName, externalJobID.String(), streamID), + } + rejectedSpec = &feeds.JobProposalSpec{ + ID: 20, + Status: feeds.SpecStatusRejected, + JobProposalID: jp.ID, + Version: 1, + Definition: fmt.Sprintf(StreamTestSpecTemplate, streamName, externalJobID.String(), streamID), + } + cancelledSpec = &feeds.JobProposalSpec{ + ID: 20, + Status: feeds.SpecStatusCancelled, + JobProposalID: jp.ID, + Version: 1, + Definition: fmt.Sprintf(StreamTestSpecTemplate, streamName, externalJobID.String(), streamID), + } + j = job.Job{ + ID: 1, + ExternalJobID: externalJobID, + } + ) + + testCases := []struct { + name string + httpTimeout *commonconfig.Duration + before func(svc *TestService) + id int64 + force bool + wantErr string + }{ + { + name: "pending job success", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(int32(0), sql.ErrNoRows) + + svc.spawner. + On("CreateJob", + mock.Anything, + mock.Anything, + mock.MatchedBy(func(j *job.Job) bool { + return j.Name.String == streamName + }), + ). + Run(func(args mock.Arguments) { (args.Get(2).(*job.Job)).ID = 1 }). + Return(nil) + svc.orm.On("ApproveSpec", + mock.Anything, + spec.ID, + externalJobID, + ).Return(nil) + svc.fmsClient.On("ApprovedJob", + mock.MatchedBy(func(ctx context.Context) bool { return true }), + &proto.ApprovedJobRequest{ + Uuid: jp.RemoteUUID.String(), + Version: int64(spec.Version), + }, + ).Return(&proto.ApprovedJobResponse{}, nil) + svc.orm.On("CountJobProposalsByStatus", mock.Anything).Return(&feeds.JobProposalCounts{}, nil) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: spec.ID, + force: false, + }, + { + name: "cancelled spec success when it is the latest spec", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.orm.On("GetSpec", mock.Anything, cancelledSpec.ID, mock.Anything).Return(cancelledSpec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.orm.On("GetLatestSpec", mock.Anything, cancelledSpec.JobProposalID).Return(cancelledSpec, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(int32(0), sql.ErrNoRows) + + svc.spawner. + On("CreateJob", + mock.Anything, + mock.Anything, + mock.MatchedBy(func(j *job.Job) bool { + return j.Name.String == streamName + }), + ). + Run(func(args mock.Arguments) { (args.Get(2).(*job.Job)).ID = 1 }). + Return(nil) + svc.orm.On("ApproveSpec", + mock.Anything, + cancelledSpec.ID, + externalJobID, + ).Return(nil) + svc.fmsClient.On("ApprovedJob", + mock.MatchedBy(func(ctx context.Context) bool { return true }), + &proto.ApprovedJobRequest{ + Uuid: jp.RemoteUUID.String(), + Version: int64(spec.Version), + }, + ).Return(&proto.ApprovedJobResponse{}, nil) + svc.orm.On("CountJobProposalsByStatus", mock.Anything).Return(&feeds.JobProposalCounts{}, nil) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: cancelledSpec.ID, + force: false, + }, + { + name: "cancelled spec failed not latest spec", + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, cancelledSpec.ID, mock.Anything).Return(cancelledSpec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.orm.On("GetLatestSpec", mock.Anything, cancelledSpec.JobProposalID).Return(&feeds.JobProposalSpec{ + ID: 21, + Status: feeds.SpecStatusPending, + JobProposalID: jp.ID, + Version: 2, + Definition: StreamTestSpecTemplate, + }, nil) + }, + id: cancelledSpec.ID, + force: false, + wantErr: "cannot approve a cancelled spec", + }, + { + name: "rejected spec failed cannot be approved", + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, cancelledSpec.ID, mock.Anything).Return(rejectedSpec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + }, + id: rejectedSpec.ID, + force: false, + wantErr: "cannot approve a rejected spec", + }, + { + name: "already existing job replacement error", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(j.ID, nil) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: spec.ID, + force: false, + wantErr: "could not approve job proposal: a job for this contract address already exists - please use the 'force' option to replace it", + }, + { + name: "already existing self managed job replacement success if forced without feedID", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + svc.orm.EXPECT().GetApprovedSpec(mock.Anything, jp.ID).Return(nil, sql.ErrNoRows) + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(j.ID, nil) + svc.spawner.On("DeleteJob", mock.Anything, mock.Anything, j.ID).Return(nil) + + svc.spawner. + On("CreateJob", + mock.Anything, + mock.Anything, + mock.MatchedBy(func(j *job.Job) bool { + return j.Name.String == streamName + }), + ). + Run(func(args mock.Arguments) { (args.Get(2).(*job.Job)).ID = 1 }). + Return(nil) + svc.orm.On("ApproveSpec", + mock.Anything, + spec.ID, + externalJobID, + ).Return(nil) + svc.fmsClient.On("ApprovedJob", + mock.MatchedBy(func(ctx context.Context) bool { return true }), + &proto.ApprovedJobRequest{ + Uuid: jp.RemoteUUID.String(), + Version: int64(spec.Version), + }, + ).Return(&proto.ApprovedJobResponse{}, nil) + svc.orm.On("CountJobProposalsByStatus", mock.Anything).Return(&feeds.JobProposalCounts{}, nil) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: spec.ID, + force: true, + }, + { + name: "already existing self managed job replacement success if forced with feedID", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(&feeds.JobProposalSpec{ + ID: 20, + Status: feeds.SpecStatusPending, + JobProposalID: jp.ID, + Version: 1, + Definition: fmt.Sprintf(StreamTestSpecTemplate, streamName, externalJobID.String(), streamID), + }, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + svc.orm.EXPECT().GetApprovedSpec(mock.Anything, jp.ID).Return(nil, sql.ErrNoRows) + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(j.ID, nil) + svc.spawner.On("DeleteJob", mock.Anything, mock.Anything, j.ID).Return(nil) + + svc.spawner. + On("CreateJob", + mock.Anything, + mock.Anything, + mock.MatchedBy(func(j *job.Job) bool { + return j.Name.String == streamName + }), + ). + Run(func(args mock.Arguments) { (args.Get(2).(*job.Job)).ID = 1 }). + Return(nil) + svc.orm.On("ApproveSpec", + mock.Anything, + spec.ID, + externalJobID, + ).Return(nil) + svc.fmsClient.On("ApprovedJob", + mock.MatchedBy(func(ctx context.Context) bool { return true }), + &proto.ApprovedJobRequest{ + Uuid: jp.RemoteUUID.String(), + Version: int64(spec.Version), + }, + ).Return(&proto.ApprovedJobResponse{}, nil) + svc.orm.On("CountJobProposalsByStatus", mock.Anything).Return(&feeds.JobProposalCounts{}, nil) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: spec.ID, + force: true, + }, + { + name: "already existing FMS managed job replacement success if forced", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + svc.orm.EXPECT().GetApprovedSpec(mock.Anything, jp.ID).Return(&feeds.JobProposalSpec{ID: 100}, nil) + svc.orm.EXPECT().CancelSpec(mock.Anything, int64(100)).Return(nil) + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(j.ID, nil) + svc.spawner.On("DeleteJob", mock.Anything, mock.Anything, j.ID).Return(nil) + + svc.spawner. + On("CreateJob", + mock.Anything, + mock.Anything, + mock.MatchedBy(func(j *job.Job) bool { + return j.Name.String == streamName + }), + ). + Run(func(args mock.Arguments) { (args.Get(2).(*job.Job)).ID = 1 }). + Return(nil) + svc.orm.On("ApproveSpec", + mock.Anything, + spec.ID, + externalJobID, + ).Return(nil) + svc.fmsClient.On("ApprovedJob", + mock.MatchedBy(func(ctx context.Context) bool { return true }), + &proto.ApprovedJobRequest{ + Uuid: jp.RemoteUUID.String(), + Version: int64(spec.Version), + }, + ).Return(&proto.ApprovedJobResponse{}, nil) + svc.orm.On("CountJobProposalsByStatus", mock.Anything).Return(&feeds.JobProposalCounts{}, nil) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: spec.ID, + force: true, + }, + { + name: "spec does not exist", + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(nil, errors.New("Not Found")) + }, + id: spec.ID, + force: false, + wantErr: "orm: job proposal spec: Not Found", + }, + { + name: "cannot approve an approved spec", + before: func(svc *TestService) { + aspec := &feeds.JobProposalSpec{ + ID: spec.ID, + JobProposalID: jp.ID, + Status: feeds.SpecStatusApproved, + } + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(aspec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + }, + id: spec.ID, + force: false, + wantErr: "cannot approve an approved spec", + }, + { + name: "cannot approved a rejected spec", + before: func(svc *TestService) { + rspec := &feeds.JobProposalSpec{ + ID: spec.ID, + JobProposalID: jp.ID, + Status: feeds.SpecStatusRejected, + } + svc.orm.On("GetSpec", mock.Anything, rspec.ID, mock.Anything).Return(rspec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + }, + id: spec.ID, + force: false, + wantErr: "cannot approve a rejected spec", + }, + { + name: "job proposal does not exist", + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(nil, errors.New("Not Found")) + }, + id: spec.ID, + wantErr: "orm: job proposal: Not Found", + }, + { + name: "bridges do not exist", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(errors.New("bridges do not exist")) + }, + id: spec.ID, + wantErr: "failed to approve job spec due to bridge check: bridges do not exist", + }, + { + name: "rpc client not connected", + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(nil, errors.New("Not Connected")) + }, + id: spec.ID, + force: false, + wantErr: "fms rpc client: Not Connected", + }, + { + name: "create job error", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(int32(0), sql.ErrNoRows) + + svc.spawner. + On("CreateJob", + mock.Anything, + mock.Anything, + mock.MatchedBy(func(j *job.Job) bool { + return j.Name.String == streamName + }), + ). + Return(errors.New("could not save")) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: spec.ID, + force: false, + wantErr: "could not approve job proposal: could not save", + }, + { + name: "approve spec orm error", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(int32(0), sql.ErrNoRows) + + svc.spawner. + On("CreateJob", + mock.Anything, + mock.Anything, + mock.MatchedBy(func(j *job.Job) bool { + return j.Name.String == streamName + }), + ). + Run(func(args mock.Arguments) { (args.Get(2).(*job.Job)).ID = 1 }). + Return(nil) + svc.orm.On("ApproveSpec", + mock.Anything, + spec.ID, + externalJobID, + ).Return(errors.New("failure")) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: spec.ID, + force: false, + wantErr: "could not approve job proposal: failure", + }, + { + name: "fms call error", + httpTimeout: commonconfig.MustNewDuration(1 * time.Minute), + before: func(svc *TestService) { + svc.orm.On("GetSpec", mock.Anything, spec.ID).Return(spec, nil) + svc.orm.On("GetJobProposal", mock.Anything, jp.ID).Return(jp, nil) + svc.connMgr.On("GetClient", jp.FeedsManagerID).Return(svc.fmsClient, nil) + svc.jobORM.On("AssertBridgesExist", mock.Anything, mock.IsType(pipeline.Pipeline{})).Return(nil) + + svc.jobORM.On("FindJobByExternalJobID", mock.Anything, externalJobID).Return(job.Job{}, sql.ErrNoRows) + svc.jobORM.On("FindJobIDByStreamID", mock.Anything, mock.Anything).Return(int32(0), sql.ErrNoRows) + + svc.spawner. + On("CreateJob", + mock.Anything, + mock.Anything, + mock.MatchedBy(func(j *job.Job) bool { + return j.Name.String == streamName + }), + ). + Run(func(args mock.Arguments) { (args.Get(2).(*job.Job)).ID = 1 }). + Return(nil) + svc.orm.On("ApproveSpec", + mock.Anything, + spec.ID, + externalJobID, + ).Return(nil) + svc.fmsClient.On("ApprovedJob", + mock.MatchedBy(func(ctx context.Context) bool { return true }), + &proto.ApprovedJobRequest{ + Uuid: jp.RemoteUUID.String(), + Version: int64(spec.Version), + }, + ).Return(nil, errors.New("failure")) + svc.orm.On("WithDataSource", mock.Anything).Return(feeds.ORM(svc.orm)) + svc.jobORM.On("WithDataSource", mock.Anything).Return(job.ORM(svc.jobORM)) + }, + id: spec.ID, + force: false, + wantErr: "could not approve job proposal: failure", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + svc := setupTestServiceCfg(t, func(c *chainlink.Config, s *chainlink.Secrets) { + c.OCR2.Enabled = testutils.Ptr(true) + if tc.httpTimeout != nil { + c.JobPipeline.HTTPRequest.DefaultTimeout = tc.httpTimeout + } + }) + + if tc.before != nil { + tc.before(svc) + } + + err := svc.ApproveSpec(ctx, tc.id, tc.force) + + if tc.wantErr != "" { + require.Error(t, err) + assert.EqualError(t, err, tc.wantErr) + } else { + require.NoError(t, err) + } + }) + } +} + func Test_Service_ApproveSpec_Bootstrap(t *testing.T) { address := "0x613a38AC1659769640aaE063C651F48E0250454C" feedIDHex := "0x0000000000000000000000000000000000000000000000000000000000000001" diff --git a/core/services/job/mocks/orm.go b/core/services/job/mocks/orm.go index 89426b55a21..96513866f37 100644 --- a/core/services/job/mocks/orm.go +++ b/core/services/job/mocks/orm.go @@ -601,6 +601,63 @@ func (_c *ORM_FindJobIDByCapabilityNameAndVersion_Call) RunAndReturn(run func(co return _c } +// FindJobIDByStreamID provides a mock function with given fields: ctx, streamID +func (_m *ORM) FindJobIDByStreamID(ctx context.Context, streamID uint32) (int32, error) { + ret := _m.Called(ctx, streamID) + + if len(ret) == 0 { + panic("no return value specified for FindJobIDByStreamID") + } + + var r0 int32 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint32) (int32, error)); ok { + return rf(ctx, streamID) + } + if rf, ok := ret.Get(0).(func(context.Context, uint32) int32); ok { + r0 = rf(ctx, streamID) + } else { + r0 = ret.Get(0).(int32) + } + + if rf, ok := ret.Get(1).(func(context.Context, uint32) error); ok { + r1 = rf(ctx, streamID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ORM_FindJobIDByStreamID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FindJobIDByStreamID' +type ORM_FindJobIDByStreamID_Call struct { + *mock.Call +} + +// FindJobIDByStreamID is a helper method to define mock.On call +// - ctx context.Context +// - streamID uint32 +func (_e *ORM_Expecter) FindJobIDByStreamID(ctx interface{}, streamID interface{}) *ORM_FindJobIDByStreamID_Call { + return &ORM_FindJobIDByStreamID_Call{Call: _e.mock.On("FindJobIDByStreamID", ctx, streamID)} +} + +func (_c *ORM_FindJobIDByStreamID_Call) Run(run func(ctx context.Context, streamID uint32)) *ORM_FindJobIDByStreamID_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint32)) + }) + return _c +} + +func (_c *ORM_FindJobIDByStreamID_Call) Return(_a0 int32, _a1 error) *ORM_FindJobIDByStreamID_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *ORM_FindJobIDByStreamID_Call) RunAndReturn(run func(context.Context, uint32) (int32, error)) *ORM_FindJobIDByStreamID_Call { + _c.Call.Return(run) + return _c +} + // FindJobIDByWorkflow provides a mock function with given fields: ctx, spec func (_m *ORM) FindJobIDByWorkflow(ctx context.Context, spec job.WorkflowSpec) (int32, error) { ret := _m.Called(ctx, spec) diff --git a/core/services/job/orm.go b/core/services/job/orm.go index 92ec9b2e83c..38e3fa492ce 100644 --- a/core/services/job/orm.go +++ b/core/services/job/orm.go @@ -78,6 +78,8 @@ type ORM interface { FindJobIDByWorkflow(ctx context.Context, spec WorkflowSpec) (int32, error) FindJobIDByCapabilityNameAndVersion(ctx context.Context, spec CCIPSpec) (int32, error) + + FindJobIDByStreamID(ctx context.Context, streamID uint32) (int32, error) } type ORMConfig interface { @@ -1334,6 +1336,20 @@ func (o *orm) FindJobsByPipelineSpecIDs(ctx context.Context, ids []int32) ([]Job return jbs, errors.Wrap(err, "FindJobsByPipelineSpecIDs failed") } +func (o *orm) FindJobIDByStreamID(ctx context.Context, streamID uint32) (jobID int32, err error) { + stmt := `SELECT id FROM jobs WHERE type = 'stream' AND stream_id = $1` + err = o.ds.GetContext(ctx, &jobID, stmt, streamID) + if err != nil { + if !errors.Is(err, sql.ErrNoRows) { + err = errors.Wrap(err, "error searching for job by stream id") + } + err = errors.Wrap(err, "FindJobIDByStreamID failed") + return + } + + return +} + // PipelineRuns returns pipeline runs for a job, with spec and taskruns loaded, latest first // If jobID is nil, returns all pipeline runs func (o *orm) PipelineRuns(ctx context.Context, jobID *int32, offset, size int) (runs []pipeline.Run, count int, err error) { diff --git a/core/services/relay/evm/capabilities/workflows/syncer/workflow_syncer_test.go b/core/services/relay/evm/capabilities/workflows/syncer/workflow_syncer_test.go index 7471c7169ea..cf2fb59a93b 100644 --- a/core/services/relay/evm/capabilities/workflows/syncer/workflow_syncer_test.go +++ b/core/services/relay/evm/capabilities/workflows/syncer/workflow_syncer_test.go @@ -14,6 +14,7 @@ import ( "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/custmsg" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -48,7 +49,16 @@ func newTestEvtHandler() *testEvtHandler { type testWorkflowRegistryContractLoader struct { } -func (m *testWorkflowRegistryContractLoader) LoadWorkflows(ctx context.Context) (*types.Head, error) { +type testDonNotifier struct { + don capabilities.DON + err error +} + +func (t *testDonNotifier) WaitForDon(ctx context.Context) (capabilities.DON, error) { + return t.don, t.err +} + +func (m *testWorkflowRegistryContractLoader) LoadWorkflows(ctx context.Context, don capabilities.DON) (*types.Head, error) { return &types.Head{ Height: "0", Hash: nil, @@ -57,7 +67,6 @@ func (m *testWorkflowRegistryContractLoader) LoadWorkflows(ctx context.Context) } func Test_InitialStateSync(t *testing.T) { - ctx := coretestutils.Context(t) lggr := logger.TestLogger(t) backendTH := testutils.NewEVMBackendTH(t) donID := uint32(1) @@ -67,29 +76,6 @@ func Test_InitialStateSync(t *testing.T) { backendTH.Backend.Commit() require.NoError(t, err) - // Build the ContractReader config - contractReaderCfg := evmtypes.ChainReaderConfig{ - Contracts: map[string]evmtypes.ChainContractReader{ - syncer.WorkflowRegistryContractName: { - ContractABI: workflow_registry_wrapper.WorkflowRegistryABI, - Configs: map[string]*evmtypes.ChainReaderDefinition{ - syncer.GetWorkflowMetadataListByDONMethodName: { - ChainSpecificName: syncer.GetWorkflowMetadataListByDONMethodName, - }, - }, - }, - }, - } - - contractReaderCfgBytes, err := json.Marshal(contractReaderCfg) - require.NoError(t, err) - - contractReader, err := backendTH.NewContractReader(ctx, t, contractReaderCfgBytes) - require.NoError(t, err) - - err = contractReader.Bind(ctx, []types.BoundContract{{Name: syncer.WorkflowRegistryContractName, Address: wfRegistryAddr.Hex()}}) - require.NoError(t, err) - // setup contract state to allow the secrets to be updated updateAllowedDONs(t, backendTH, wfRegistryC, []uint32{donID}, true) updateAuthorizedAddress(t, backendTH, wfRegistryC, []common.Address{backendTH.ContractsOwner.From}, true) @@ -112,24 +98,37 @@ func Test_InitialStateSync(t *testing.T) { } testEventHandler := newTestEvtHandler() - loader := syncer.NewWorkflowRegistryContractLoader(wfRegistryAddr.Hex(), donID, contractReader, testEventHandler) + loader := syncer.NewWorkflowRegistryContractLoader(wfRegistryAddr.Hex(), func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return backendTH.NewContractReader(ctx, t, bytes) + }, testEventHandler) // Create the worker worker := syncer.NewWorkflowRegistry( lggr, - contractReader, + func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return backendTH.NewContractReader(ctx, t, bytes) + }, wfRegistryAddr.Hex(), syncer.WorkflowEventPollerConfig{ QueryCount: 20, }, testEventHandler, loader, + &testDonNotifier{ + don: capabilities.DON{ + ID: donID, + }, + err: nil, + }, syncer.WithTicker(make(chan time.Time)), ) servicetest.Run(t, worker) - assert.Len(t, testEventHandler.events, numberWorkflows) + require.Eventually(t, func() bool { + return len(testEventHandler.events) == numberWorkflows + }, 5*time.Second, time.Second) + for _, event := range testEventHandler.events { assert.Equal(t, syncer.WorkflowRegisteredEvent, event.GetEventType()) } @@ -227,10 +226,17 @@ func Test_SecretsWorker(t *testing.T) { handler := syncer.NewEventHandler(lggr, orm, fetcherFn, nil, nil, emitter, clockwork.NewFakeClock(), workflowkey.Key{}) - worker := syncer.NewWorkflowRegistry(lggr, contractReader, wfRegistryAddr.Hex(), + worker := syncer.NewWorkflowRegistry(lggr, func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { + return contractReader, nil + }, wfRegistryAddr.Hex(), syncer.WorkflowEventPollerConfig{ QueryCount: 20, - }, handler, &testWorkflowRegistryContractLoader{}, syncer.WithTicker(giveTicker.C)) + }, handler, &testWorkflowRegistryContractLoader{}, &testDonNotifier{ + don: capabilities.DON{ + ID: donID, + }, + err: nil, + }, syncer.WithTicker(giveTicker.C)) // setup contract state to allow the secrets to be updated updateAllowedDONs(t, backendTH, wfRegistryC, []uint32{donID}, true) diff --git a/core/services/workflows/syncer/fetcher.go b/core/services/workflows/syncer/fetcher.go index ed815a240ba..bebdfb0519e 100644 --- a/core/services/workflows/syncer/fetcher.go +++ b/core/services/workflows/syncer/fetcher.go @@ -13,7 +13,6 @@ import ( ) func NewFetcherFunc( - ctx context.Context, lggr logger.Logger, och *webapi.OutgoingConnectorHandler) FetcherFunc { return func(ctx context.Context, url string) ([]byte, error) { diff --git a/core/services/workflows/syncer/fetcher_test.go b/core/services/workflows/syncer/fetcher_test.go index 846a9186b5a..4ed228c6a51 100644 --- a/core/services/workflows/syncer/fetcher_test.go +++ b/core/services/workflows/syncer/fetcher_test.go @@ -46,7 +46,7 @@ func TestNewFetcherFunc(t *testing.T) { connector.EXPECT().DonID().Return("don-id") connector.EXPECT().GatewayIDs().Return([]string{"gateway1", "gateway2"}) - fetcher := NewFetcherFunc(ctx, lggr, och) + fetcher := NewFetcherFunc(lggr, och) payload, err := fetcher(ctx, url) require.NoError(t, err) diff --git a/core/services/workflows/syncer/workflow_registry.go b/core/services/workflows/syncer/workflow_registry.go index 4f3bb76bd14..6642679b228 100644 --- a/core/services/workflows/syncer/workflow_registry.go +++ b/core/services/workflows/syncer/workflow_registry.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/services" types "github.com/smartcontractkit/chainlink-common/pkg/types" query "github.com/smartcontractkit/chainlink-common/pkg/types/query" @@ -111,12 +112,8 @@ type workflowRegistry struct { lggr logger.Logger workflowRegistryAddress string - reader ContractReader - // initReader allows the workflowRegistry to initialize a contract reader if one is not provided - // and separates the contract reader initialization from the workflowRegistry start up. - initReader func(context.Context, logger.Logger, ContractReaderFactory, types.BoundContract) (types.ContractReader, error) - relayer ContractReaderFactory + newContractReaderFn newContractReaderFn eventPollerCfg WorkflowEventPollerConfig eventTypes []WorkflowRegistryEventType @@ -132,6 +129,10 @@ type workflowRegistry struct { // heap is a min heap that merges batches of events from the contract query goroutines. The // default min heap is sorted by block height. heap Heap + + workflowDonNotifier donNotifier + + reader ContractReader } // WithTicker allows external callers to provide a ticker to the workflowRegistry. This is useful @@ -142,12 +143,6 @@ func WithTicker(ticker <-chan time.Time) func(*workflowRegistry) { } } -func WithReader(reader types.ContractReader) func(*workflowRegistry) { - return func(wr *workflowRegistry) { - wr.reader = reader - } -} - type evtHandler interface { Handle(ctx context.Context, event Event) error } @@ -155,27 +150,33 @@ type evtHandler interface { type initialWorkflowsStateLoader interface { // LoadWorkflows loads all the workflows for the given donID from the contract. Returns the head of the chain as of the // point in time at which the load occurred. - LoadWorkflows(ctx context.Context) (*types.Head, error) + LoadWorkflows(ctx context.Context, don capabilities.DON) (*types.Head, error) } +type donNotifier interface { + WaitForDon(ctx context.Context) (capabilities.DON, error) +} + +type newContractReaderFn func(context.Context, []byte) (ContractReader, error) + // NewWorkflowRegistry returns a new workflowRegistry. // Only queries for WorkflowRegistryForceUpdateSecretsRequestedV1 events. func NewWorkflowRegistry( lggr logger.Logger, - reader ContractReader, + newContractReaderFn newContractReaderFn, addr string, eventPollerConfig WorkflowEventPollerConfig, handler evtHandler, initialWorkflowsStateLoader initialWorkflowsStateLoader, + workflowDonNotifier donNotifier, opts ...func(*workflowRegistry), ) *workflowRegistry { ets := []WorkflowRegistryEventType{ForceUpdateSecretsEvent} wr := &workflowRegistry{ lggr: lggr.Named(name), + newContractReaderFn: newContractReaderFn, workflowRegistryAddress: addr, - reader: reader, eventPollerCfg: eventPollerConfig, - initReader: newReader, heap: newBlockHeightHeap(), stopCh: make(services.StopChan), eventTypes: ets, @@ -183,6 +184,7 @@ func NewWorkflowRegistry( batchCh: make(chan []WorkflowRegistryEventResponse, len(ets)), handler: handler, initialWorkflowsStateLoader: initialWorkflowsStateLoader, + workflowDonNotifier: workflowDonNotifier, } for _, opt := range opts { @@ -193,13 +195,8 @@ func NewWorkflowRegistry( // Start starts the workflowRegistry. It starts two goroutines, one for querying the contract // and one for handling the events. -func (w *workflowRegistry) Start(ctx context.Context) error { +func (w *workflowRegistry) Start(_ context.Context) error { return w.StartOnce(w.Name(), func() error { - loadWorkflowsHead, err := w.initialWorkflowsStateLoader.LoadWorkflows(ctx) - if err != nil { - return fmt.Errorf("failed to load workflows: %w", err) - } - ctx, cancel := w.stopCh.NewCtx() w.wg.Add(1) @@ -207,6 +204,18 @@ func (w *workflowRegistry) Start(ctx context.Context) error { defer w.wg.Done() defer cancel() + don, err := w.workflowDonNotifier.WaitForDon(ctx) + if err != nil { + w.lggr.Errorf("failed to wait for don: %v", err) + return + } + + loadWorkflowsHead, err := w.initialWorkflowsStateLoader.LoadWorkflows(ctx, don) + if err != nil { + w.lggr.Errorf("failed to load workflows: %v", err) + return + } + w.syncEventsLoop(ctx, loadWorkflowsHead.Height) }() @@ -394,7 +403,7 @@ func (w *workflowRegistry) getContractReader(ctx context.Context) (ContractReade } if w.reader == nil { - reader, err := w.initReader(ctx, w.lggr, w.relayer, c) + reader, err := getWorkflowRegistryEventReader(ctx, w.newContractReaderFn, c) if err != nil { return nil, err } @@ -490,12 +499,11 @@ func queryEvent( } } -func newReader( +func getWorkflowRegistryEventReader( ctx context.Context, - lggr logger.Logger, - factory ContractReaderFactory, + newReaderFn newContractReaderFn, bc types.BoundContract, -) (types.ContractReader, error) { +) (ContractReader, error) { contractReaderCfg := evmtypes.ChainReaderConfig{ Contracts: map[string]evmtypes.ChainContractReader{ WorkflowRegistryContractName: { @@ -518,7 +526,7 @@ func newReader( return nil, err } - reader, err := factory.NewContractReader(ctx, marshalledCfg) + reader, err := newReaderFn(ctx, marshalledCfg) if err != nil { return nil, err } @@ -546,26 +554,52 @@ func (r workflowAsEvent) GetData() any { type workflowRegistryContractLoader struct { workflowRegistryAddress string - donID uint32 - reader ContractReader + newContractReaderFn newContractReaderFn handler evtHandler } func NewWorkflowRegistryContractLoader( workflowRegistryAddress string, - donID uint32, - reader ContractReader, + newContractReaderFn newContractReaderFn, handler evtHandler, ) *workflowRegistryContractLoader { return &workflowRegistryContractLoader{ workflowRegistryAddress: workflowRegistryAddress, - donID: donID, - reader: reader, + newContractReaderFn: newContractReaderFn, handler: handler, } } -func (l *workflowRegistryContractLoader) LoadWorkflows(ctx context.Context) (*types.Head, error) { +func (l *workflowRegistryContractLoader) LoadWorkflows(ctx context.Context, don capabilities.DON) (*types.Head, error) { + // Build the ContractReader config + contractReaderCfg := evmtypes.ChainReaderConfig{ + Contracts: map[string]evmtypes.ChainContractReader{ + WorkflowRegistryContractName: { + ContractABI: workflow_registry_wrapper.WorkflowRegistryABI, + Configs: map[string]*evmtypes.ChainReaderDefinition{ + GetWorkflowMetadataListByDONMethodName: { + ChainSpecificName: GetWorkflowMetadataListByDONMethodName, + }, + }, + }, + }, + } + + contractReaderCfgBytes, err := json.Marshal(contractReaderCfg) + if err != nil { + return nil, fmt.Errorf("failed to marshal contract reader config: %w", err) + } + + contractReader, err := l.newContractReaderFn(ctx, contractReaderCfgBytes) + if err != nil { + return nil, fmt.Errorf("failed to create contract reader: %w", err) + } + + err = contractReader.Bind(ctx, []types.BoundContract{{Name: WorkflowRegistryContractName, Address: l.workflowRegistryAddress}}) + if err != nil { + return nil, fmt.Errorf("failed to bind contract reader: %w", err) + } + contractBinding := types.BoundContract{ Address: l.workflowRegistryAddress, Name: WorkflowRegistryContractName, @@ -573,7 +607,7 @@ func (l *workflowRegistryContractLoader) LoadWorkflows(ctx context.Context) (*ty readIdentifier := contractBinding.ReadIdentifier(GetWorkflowMetadataListByDONMethodName) params := GetWorkflowMetadataListByDONParams{ - DonID: l.donID, + DonID: don.ID, Start: 0, Limit: 0, // 0 tells the contract to return max pagination limit workflows on each call } @@ -582,7 +616,7 @@ func (l *workflowRegistryContractLoader) LoadWorkflows(ctx context.Context) (*ty for { var err error var workflows GetWorkflowMetadataListByDONReturnVal - headAtLastRead, err = l.reader.GetLatestValueWithHeadData(ctx, readIdentifier, primitives.Finalized, params, &workflows) + headAtLastRead, err = contractReader.GetLatestValueWithHeadData(ctx, readIdentifier, primitives.Finalized, params, &workflows) if err != nil { return nil, fmt.Errorf("failed to get workflow metadata for don %w", err) } diff --git a/core/services/workflows/syncer/workflow_registry_test.go b/core/services/workflows/syncer/workflow_registry_test.go index 17a71d73030..0cccb405710 100644 --- a/core/services/workflows/syncer/workflow_registry_test.go +++ b/core/services/workflows/syncer/workflow_registry_test.go @@ -10,6 +10,7 @@ import ( "github.com/jonboulle/clockwork" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/custmsg" "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" types "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -26,6 +27,15 @@ import ( "github.com/stretchr/testify/require" ) +type testDonNotifier struct { + don capabilities.DON + err error +} + +func (t *testDonNotifier) WaitForDon(ctx context.Context) (capabilities.DON, error) { + return t.don, t.err +} + func Test_Workflow_Registry_Syncer(t *testing.T) { var ( giveContents = "contents" @@ -62,12 +72,23 @@ func Test_Workflow_Registry_Syncer(t *testing.T) { handler = NewEventHandler(lggr, orm, gateway, nil, nil, emitter, clockwork.NewFakeClock(), workflowkey.Key{}) - loader = NewWorkflowRegistryContractLoader(contractAddress, 1, reader, handler) + loader = NewWorkflowRegistryContractLoader(contractAddress, func(ctx context.Context, bytes []byte) (ContractReader, error) { + return reader, nil + }, handler) - worker = NewWorkflowRegistry(lggr, reader, contractAddress, + worker = NewWorkflowRegistry(lggr, func(ctx context.Context, bytes []byte) (ContractReader, error) { + return reader, nil + }, contractAddress, WorkflowEventPollerConfig{ QueryCount: 20, - }, handler, loader, WithTicker(ticker)) + }, handler, loader, + &testDonNotifier{ + don: capabilities.DON{ + ID: 1, + }, + err: nil, + }, + WithTicker(ticker)) ) // Cleanup the worker @@ -100,6 +121,7 @@ func Test_Workflow_Registry_Syncer(t *testing.T) { reader.EXPECT().GetLatestValueWithHeadData(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&types.Head{ Height: "0", }, nil) + reader.EXPECT().Bind(mock.Anything, mock.Anything).Return(nil) // Go run the worker servicetest.Run(t, worker) diff --git a/deployment/address_book.go b/deployment/address_book.go index 3125313a841..7997507554f 100644 --- a/deployment/address_book.go +++ b/deployment/address_book.go @@ -27,6 +27,7 @@ var ( Version1_1_0 = *semver.MustParse("1.1.0") Version1_2_0 = *semver.MustParse("1.2.0") Version1_5_0 = *semver.MustParse("1.5.0") + Version1_5_1 = *semver.MustParse("1.5.1") Version1_6_0_dev = *semver.MustParse("1.6.0-dev") ) diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 61b58b59af6..add99386a31 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -3,6 +3,9 @@ package changeset import ( "fmt" + burn_mint_token_pool "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/burn_mint_token_pool_1_4_0" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/erc20" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_messenger" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_transmitter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/usdc_token_pool" @@ -75,7 +78,8 @@ type CCIPChainState struct { // and the respective token contract // This is more of an illustration of how we'll have tokens, and it might need some work later to work properly. // Not all tokens will be burn and mint tokens. - BurnMintTokens677 map[TokenSymbol]*burn_mint_erc677.BurnMintERC677 + BurnMintTokens677 map[TokenSymbol]*burn_mint_erc677.BurnMintERC677 + BurnMintTokenPools map[TokenSymbol]*burn_mint_token_pool.BurnMintTokenPool // Map between token Symbol (e.g. LinkSymbol, WethSymbol) // and the respective aggregator USD feed contract USDFeeds map[TokenSymbol]*aggregator_v3_interface.AggregatorV3Interface @@ -438,6 +442,40 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, fmt.Errorf("unknown feed description %s", desc) } state.USDFeeds[key] = feed + case deployment.NewTypeAndVersion(BurnMintTokenPool, deployment.Version1_5_1).String(): + pool, err := burn_mint_token_pool.NewBurnMintTokenPool(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + if state.BurnMintTokenPools == nil { + state.BurnMintTokenPools = make(map[TokenSymbol]*burn_mint_token_pool.BurnMintTokenPool) + } + tokAddress, err := pool.GetToken(nil) + if err != nil { + return state, err + } + tok, err := erc20.NewERC20(tokAddress, chain.Client) + if err != nil { + return state, err + } + symbol, err := tok.Symbol(nil) + if err != nil { + return state, err + } + state.BurnMintTokenPools[TokenSymbol(symbol)] = pool + case deployment.NewTypeAndVersion(BurnMintToken, deployment.Version1_0_0).String(): + tok, err := burn_mint_erc677.NewBurnMintERC677(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + if state.BurnMintTokens677 == nil { + state.BurnMintTokens677 = make(map[TokenSymbol]*burn_mint_erc677.BurnMintERC677) + } + symbol, err := tok.Symbol(nil) + if err != nil { + return state, fmt.Errorf("failed to get token symbol of token at %s: %w", address, err) + } + state.BurnMintTokens677[TokenSymbol(symbol)] = tok default: return state, fmt.Errorf("unknown contract %s", tvStr) } diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index b03d49f47f0..ea1cea2c790 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -11,6 +11,8 @@ import ( "testing" "time" + "golang.org/x/sync/errgroup" + mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common/hexutil" @@ -33,13 +35,15 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" - "github.com/smartcontractkit/chainlink-ccip/pkg/reader" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink-ccip/pkg/reader" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/environment/devenv" @@ -255,8 +259,9 @@ func mockAttestationResponse() *httptest.Server { } type TestConfigs struct { - IsUSDC bool - IsMultiCall3 bool + IsUSDC bool + IsMultiCall3 bool + OCRConfigOverride func(CCIPOCRParams) CCIPOCRParams } func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, numChains int, numNodes int, tCfg *TestConfigs) DeployedEnv { @@ -743,63 +748,102 @@ func DeployTransferableToken( lggr logger.Logger, chains map[uint64]deployment.Chain, src, dst uint64, + srcActor, dstActor *bind.TransactOpts, state CCIPOnChainState, addresses deployment.AddressBook, token string, ) (*burn_mint_erc677.BurnMintERC677, *burn_mint_token_pool.BurnMintTokenPool, *burn_mint_erc677.BurnMintERC677, *burn_mint_token_pool.BurnMintTokenPool, error) { // Deploy token and pools - srcToken, srcPool, err := deployTransferTokenOneEnd(lggr, chains[src], addresses, token) + srcToken, srcPool, dstToken, dstPool, err := deployTokenPoolsInParallel(lggr, chains, src, dst, srcActor, dstActor, state, addresses, token) if err != nil { return nil, nil, nil, nil, err } - dstToken, dstPool, err := deployTransferTokenOneEnd(lggr, chains[dst], addresses, token) - if err != nil { - return nil, nil, nil, nil, err - } - - // Attach token pools to registry - if err := attachTokenToTheRegistry(chains[src], state.Chains[src], chains[src].DeployerKey, srcToken.Address(), srcPool.Address()); err != nil { - return nil, nil, nil, nil, err - } - - if err := attachTokenToTheRegistry(chains[dst], state.Chains[dst], chains[dst].DeployerKey, dstToken.Address(), dstPool.Address()); err != nil { - return nil, nil, nil, nil, err - } - // Connect pool to each other - if err := setTokenPoolCounterPart(chains[src], srcPool, dst, dstToken.Address(), dstPool.Address()); err != nil { - return nil, nil, nil, nil, err - } - - if err := setTokenPoolCounterPart(chains[dst], dstPool, src, srcToken.Address(), srcPool.Address()); err != nil { + // Configure pools in parallel + configurePoolGrp := errgroup.Group{} + configurePoolGrp.Go(func() error { + err := setTokenPoolCounterPart(chains[src], srcPool, srcActor, dst, dstToken.Address(), dstPool.Address()) + if err != nil { + return fmt.Errorf("failed to set token pool counter part chain %d: %w", src, err) + } + err = grantMintBurnPermissions(lggr, chains[src], srcToken, srcActor, srcPool.Address()) + if err != nil { + return fmt.Errorf("failed to grant mint burn permissions chain %d: %w", src, err) + } + return nil + }) + configurePoolGrp.Go(func() error { + err := setTokenPoolCounterPart(chains[dst], dstPool, dstActor, src, srcToken.Address(), srcPool.Address()) + if err != nil { + return fmt.Errorf("failed to set token pool counter part chain %d: %w", dst, err) + } + if err := grantMintBurnPermissions(lggr, chains[dst], dstToken, dstActor, dstPool.Address()); err != nil { + return fmt.Errorf("failed to grant mint burn permissions chain %d: %w", dst, err) + } + return nil + }) + if err := configurePoolGrp.Wait(); err != nil { return nil, nil, nil, nil, err } + return srcToken, srcPool, dstToken, dstPool, nil +} - // Add burn/mint permissions - if err := grantMintBurnPermissions(lggr, chains[src], srcToken, srcPool.Address()); err != nil { +func deployTokenPoolsInParallel( + lggr logger.Logger, + chains map[uint64]deployment.Chain, + src, dst uint64, + srcActor, dstActor *bind.TransactOpts, + state CCIPOnChainState, + addresses deployment.AddressBook, + token string, +) ( + *burn_mint_erc677.BurnMintERC677, + *burn_mint_token_pool.BurnMintTokenPool, + *burn_mint_erc677.BurnMintERC677, + *burn_mint_token_pool.BurnMintTokenPool, + error, +) { + deployGrp := errgroup.Group{} + // Deploy token and pools + var srcToken *burn_mint_erc677.BurnMintERC677 + var srcPool *burn_mint_token_pool.BurnMintTokenPool + var dstToken *burn_mint_erc677.BurnMintERC677 + var dstPool *burn_mint_token_pool.BurnMintTokenPool + + deployGrp.Go(func() error { + var err error + srcToken, srcPool, err = deployTransferTokenOneEnd(lggr, chains[src], srcActor, addresses, token) + if err != nil { + return err + } + if err := attachTokenToTheRegistry(chains[src], state.Chains[src], srcActor, srcToken.Address(), srcPool.Address()); err != nil { + return err + } + return nil + }) + deployGrp.Go(func() error { + var err error + dstToken, dstPool, err = deployTransferTokenOneEnd(lggr, chains[dst], dstActor, addresses, token) + if err != nil { + return err + } + if err := attachTokenToTheRegistry(chains[dst], state.Chains[dst], dstActor, dstToken.Address(), dstPool.Address()); err != nil { + return err + } + return nil + }) + if err := deployGrp.Wait(); err != nil { return nil, nil, nil, nil, err } - - if err := grantMintBurnPermissions(lggr, chains[dst], dstToken, dstPool.Address()); err != nil { - return nil, nil, nil, nil, err + if srcToken == nil || srcPool == nil || dstToken == nil || dstPool == nil { + return nil, nil, nil, nil, fmt.Errorf("failed to deploy token and pool") } - return srcToken, srcPool, dstToken, dstPool, nil } -func grantMintBurnPermissions(lggr logger.Logger, chain deployment.Chain, token *burn_mint_erc677.BurnMintERC677, address common.Address) error { - lggr.Infow("Granting burn permissions", "token", token.Address(), "burner", address) - tx, err := token.GrantBurnRole(chain.DeployerKey, address) - if err != nil { - return err - } - _, err = chain.Confirm(tx) - if err != nil { - return err - } - - lggr.Infow("Granting mint permissions", "token", token.Address(), "minter", address) - tx, err = token.GrantMintRole(chain.DeployerKey, address) +func grantMintBurnPermissions(lggr logger.Logger, chain deployment.Chain, token *burn_mint_erc677.BurnMintERC677, actor *bind.TransactOpts, address common.Address) error { + lggr.Infow("Granting burn/mint permissions", "token", token.Address(), "address", address) + tx, err := token.GrantMintAndBurnRoles(actor, address) if err != nil { return err } @@ -811,6 +855,7 @@ func setUSDCTokenPoolCounterPart( chain deployment.Chain, tokenPool *usdc_token_pool.USDCTokenPool, destChainSelector uint64, + actor *bind.TransactOpts, destTokenAddress common.Address, destTokenPoolAddress common.Address, ) error { @@ -843,18 +888,12 @@ func setUSDCTokenPoolCounterPart( return err } - return setTokenPoolCounterPart(chain, pool, destChainSelector, destTokenAddress, destTokenPoolAddress) + return setTokenPoolCounterPart(chain, pool, actor, destChainSelector, destTokenAddress, destTokenPoolAddress) } -func setTokenPoolCounterPart( - chain deployment.Chain, - tokenPool *burn_mint_token_pool.BurnMintTokenPool, - destChainSelector uint64, - destTokenAddress common.Address, - destTokenPoolAddress common.Address, -) error { +func setTokenPoolCounterPart(chain deployment.Chain, tokenPool *burn_mint_token_pool.BurnMintTokenPool, actor *bind.TransactOpts, destChainSelector uint64, destTokenAddress common.Address, destTokenPoolAddress common.Address) error { tx, err := tokenPool.ApplyChainUpdates( - chain.DeployerKey, + actor, []uint64{}, []burn_mint_token_pool.TokenPoolChainUpdate{ { @@ -884,7 +923,7 @@ func setTokenPoolCounterPart( } tx, err = tokenPool.AddRemotePool( - chain.DeployerKey, + actor, destChainSelector, destTokenPoolAddress.Bytes(), ) @@ -944,6 +983,7 @@ func attachTokenToTheRegistry( func deployTransferTokenOneEnd( lggr logger.Logger, chain deployment.Chain, + deployer *bind.TransactOpts, addressBook deployment.AddressBook, tokenSymbol string, ) (*burn_mint_erc677.BurnMintERC677, *burn_mint_token_pool.BurnMintTokenPool, error) { @@ -969,7 +1009,7 @@ func deployTransferTokenOneEnd( tokenContract, err := deployment.DeployContract(lggr, chain, addressBook, func(chain deployment.Chain) deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677] { tokenAddress, tx, token, err2 := burn_mint_erc677.DeployBurnMintERC677( - chain.DeployerKey, + deployer, chain.Client, tokenSymbol, tokenSymbol, @@ -985,7 +1025,7 @@ func deployTransferTokenOneEnd( return nil, nil, err } - tx, err := tokenContract.Contract.GrantMintRole(chain.DeployerKey, chain.DeployerKey.From) + tx, err := tokenContract.Contract.GrantMintRole(deployer, deployer.From) if err != nil { return nil, nil, err } @@ -997,7 +1037,7 @@ func deployTransferTokenOneEnd( tokenPool, err := deployment.DeployContract(lggr, chain, addressBook, func(chain deployment.Chain) deployment.ContractDeploy[*burn_mint_token_pool.BurnMintTokenPool] { tokenPoolAddress, tx, tokenPoolContract, err2 := burn_mint_token_pool.DeployBurnMintTokenPool( - chain.DeployerKey, + deployer, chain.Client, tokenContract.Address, tokenDecimals, @@ -1006,7 +1046,7 @@ func deployTransferTokenOneEnd( common.HexToAddress(routerAddress), ) return deployment.ContractDeploy[*burn_mint_token_pool.BurnMintTokenPool]{ - tokenPoolAddress, tokenPoolContract, tx, deployment.NewTypeAndVersion(BurnMintTokenPool, deployment.Version1_0_0), err2, + tokenPoolAddress, tokenPoolContract, tx, deployment.NewTypeAndVersion(BurnMintTokenPool, deployment.Version1_5_1), err2, } }) if err != nil { @@ -1016,3 +1056,144 @@ func deployTransferTokenOneEnd( return tokenContract.Contract, tokenPool.Contract, nil } + +// MintAndAllow mints tokens for deployers and allow router to spend them +func MintAndAllow( + t *testing.T, + e deployment.Environment, + state CCIPOnChainState, + owners map[uint64]*bind.TransactOpts, + tkMap map[uint64][]*burn_mint_erc677.BurnMintERC677, +) { + configurePoolGrp := errgroup.Group{} + tenCoins := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(10)) + + for chain, tokens := range tkMap { + owner, ok := owners[chain] + require.True(t, ok) + + tokens := tokens + configurePoolGrp.Go(func() error { + for _, token := range tokens { + tx, err := token.Mint( + owner, + e.Chains[chain].DeployerKey.From, + new(big.Int).Mul(tenCoins, big.NewInt(10)), + ) + require.NoError(t, err) + _, err = e.Chains[chain].Confirm(tx) + require.NoError(t, err) + + tx, err = token.Approve(e.Chains[chain].DeployerKey, state.Chains[chain].Router.Address(), tenCoins) + require.NoError(t, err) + _, err = e.Chains[chain].Confirm(tx) + require.NoError(t, err) + } + return nil + }) + } + + require.NoError(t, configurePoolGrp.Wait()) +} + +// TransferAndWaitForSuccess sends a message from sourceChain to destChain and waits for it to be executed +func TransferAndWaitForSuccess( + ctx context.Context, + t *testing.T, + env deployment.Environment, + state CCIPOnChainState, + sourceChain, destChain uint64, + tokens []router.ClientEVMTokenAmount, + receiver common.Address, + data []byte, + expectedStatus int, +) { + identifier := SourceDestPair{ + SourceChainSelector: sourceChain, + DestChainSelector: destChain, + } + + startBlocks := make(map[uint64]*uint64) + expectedSeqNum := make(map[SourceDestPair]uint64) + expectedSeqNumExec := make(map[SourceDestPair][]uint64) + + latesthdr, err := env.Chains[destChain].Client.HeaderByNumber(ctx, nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + startBlocks[destChain] = &block + + msgSentEvent := TestSendRequest(t, env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(receiver.Bytes(), 32), + Data: data, + TokenAmounts: tokens, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + expectedSeqNum[identifier] = msgSentEvent.SequenceNumber + expectedSeqNumExec[identifier] = []uint64{msgSentEvent.SequenceNumber} + + // Wait for all commit reports to land. + ConfirmCommitForAllWithExpectedSeqNums(t, env, state, expectedSeqNum, startBlocks) + + // Wait for all exec reports to land + states := ConfirmExecWithSeqNrsForAll(t, env, state, expectedSeqNumExec, startBlocks) + require.Equal(t, expectedStatus, states[identifier][msgSentEvent.SequenceNumber]) +} + +func WaitForTheTokenBalance( + ctx context.Context, + t *testing.T, + token common.Address, + receiver common.Address, + chain deployment.Chain, + expected *big.Int, +) { + tokenContract, err := burn_mint_erc677.NewBurnMintERC677(token, chain.Client) + require.NoError(t, err) + + require.Eventually(t, func() bool { + actualBalance, err := tokenContract.BalanceOf(&bind.CallOpts{Context: ctx}, receiver) + require.NoError(t, err) + + t.Log("Waiting for the token balance", + "expected", expected, + "actual", actualBalance, + "token", token, + "receiver", receiver, + ) + + return actualBalance.Cmp(expected) == 0 + }, tests.WaitTimeout(t), 100*time.Millisecond) +} + +func GetTokenBalance( + ctx context.Context, + t *testing.T, + token common.Address, + receiver common.Address, + chain deployment.Chain, +) *big.Int { + tokenContract, err := burn_mint_erc677.NewBurnMintERC677(token, chain.Client) + require.NoError(t, err) + + balance, err := tokenContract.BalanceOf(&bind.CallOpts{Context: ctx}, receiver) + require.NoError(t, err) + + t.Log("Getting token balance", + "actual", balance, + "token", token, + "receiver", receiver, + ) + + return balance +} + +func DefaultRouterMessage(receiverAddress common.Address) router.ClientEVM2AnyMessage { + return router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(receiverAddress.Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + } +} diff --git a/deployment/ccip/changeset/test_usdc_helpers.go b/deployment/ccip/changeset/test_usdc_helpers.go index 4f96070e63c..75994ec9356 100644 --- a/deployment/ccip/changeset/test_usdc_helpers.go +++ b/deployment/ccip/changeset/test_usdc_helpers.go @@ -3,6 +3,8 @@ package changeset import ( "math/big" + "golang.org/x/sync/errgroup" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink-ccip/pkg/reader" @@ -27,53 +29,78 @@ func ConfigureUSDCTokenPools( srcPool := state.Chains[src].USDCTokenPool dstPool := state.Chains[dst].USDCTokenPool - // Attach token pools to registry - if err := attachTokenToTheRegistry(chains[src], state.Chains[src], chains[src].DeployerKey, srcToken.Address(), srcPool.Address()); err != nil { - lggr.Errorw("Failed to attach token to the registry", "err", err, "token", srcToken.Address(), "pool", srcPool.Address()) - return nil, nil, err + args := []struct { + sourceChain deployment.Chain + dstChainSel uint64 + state CCIPChainState + srcToken *burn_mint_erc677.BurnMintERC677 + srcPool *usdc_token_pool.USDCTokenPool + dstToken *burn_mint_erc677.BurnMintERC677 + dstPool *usdc_token_pool.USDCTokenPool + }{ + { + chains[src], + dst, + state.Chains[src], + srcToken, + srcPool, + dstToken, + dstPool, + }, + { + chains[dst], + src, + state.Chains[dst], + dstToken, + dstPool, + srcToken, + srcPool, + }, } - if err := attachTokenToTheRegistry(chains[dst], state.Chains[dst], chains[dst].DeployerKey, dstToken.Address(), dstPool.Address()); err != nil { - lggr.Errorw("Failed to attach token to the registry", "err", err, "token", dstToken.Address(), "pool", dstPool.Address()) - return nil, nil, err + configurePoolGrp := errgroup.Group{} + for _, arg := range args { + configurePoolGrp.Go(configureSingleChain(lggr, arg.sourceChain, arg.dstChainSel, arg.state, arg.srcToken, arg.srcPool, arg.dstToken, arg.dstPool)) } - - // Connect pool to each other - if err := setUSDCTokenPoolCounterPart(chains[src], srcPool, dst, dstToken.Address(), dstPool.Address()); err != nil { - lggr.Errorw("Failed to set counter part", "err", err, "srcPool", srcPool.Address(), "dstPool", dstPool.Address()) + if err := configurePoolGrp.Wait(); err != nil { return nil, nil, err } + return srcToken, dstToken, nil +} - if err := setUSDCTokenPoolCounterPart(chains[dst], dstPool, src, srcToken.Address(), srcPool.Address()); err != nil { - lggr.Errorw("Failed to set counter part", "err", err, "srcPool", dstPool.Address(), "dstPool", srcPool.Address()) - return nil, nil, err - } +func configureSingleChain( + lggr logger.Logger, + sourceChain deployment.Chain, + dstChainSel uint64, + state CCIPChainState, + srcToken *burn_mint_erc677.BurnMintERC677, + srcPool *usdc_token_pool.USDCTokenPool, + dstToken *burn_mint_erc677.BurnMintERC677, + dstPool *usdc_token_pool.USDCTokenPool, +) func() error { + return func() error { + if err := attachTokenToTheRegistry(sourceChain, state, sourceChain.DeployerKey, srcToken.Address(), srcPool.Address()); err != nil { + lggr.Errorw("Failed to attach token to the registry", "err", err, "token", srcToken.Address(), "pool", srcPool.Address()) + return err + } - // Add burn/mint permissions for source - for _, addr := range []common.Address{ - srcPool.Address(), - state.Chains[src].MockUSDCTokenMessenger.Address(), - state.Chains[src].MockUSDCTransmitter.Address(), - } { - if err := grantMintBurnPermissions(lggr, chains[src], srcToken, addr); err != nil { - lggr.Errorw("Failed to grant mint/burn permissions", "err", err, "token", srcToken.Address(), "minter", addr) - return nil, nil, err + if err := setUSDCTokenPoolCounterPart(sourceChain, srcPool, dstChainSel, sourceChain.DeployerKey, dstToken.Address(), dstPool.Address()); err != nil { + lggr.Errorw("Failed to set counter part", "err", err, "srcPool", srcPool.Address(), "dstPool", dstPool.Address()) + return err } - } - // Add burn/mint permissions for dest - for _, addr := range []common.Address{ - dstPool.Address(), - state.Chains[dst].MockUSDCTokenMessenger.Address(), - state.Chains[dst].MockUSDCTransmitter.Address(), - } { - if err := grantMintBurnPermissions(lggr, chains[dst], dstToken, addr); err != nil { - lggr.Errorw("Failed to grant mint/burn permissions", "err", err, "token", dstToken.Address(), "minter", addr) - return nil, nil, err + for _, addr := range []common.Address{ + srcPool.Address(), + state.MockUSDCTokenMessenger.Address(), + state.MockUSDCTransmitter.Address(), + } { + if err := grantMintBurnPermissions(lggr, sourceChain, srcToken, sourceChain.DeployerKey, addr); err != nil { + lggr.Errorw("Failed to grant mint/burn permissions", "err", err, "token", srcToken.Address(), "address", addr) + return err + } } + return nil } - - return srcToken, dstToken, nil } func UpdateFeeQuoterForUSDC( diff --git a/deployment/go.mod b/deployment/go.mod index 7b69e60d211..4e522119a27 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -22,7 +22,7 @@ require ( github.com/sethvargo/go-retry v0.2.4 github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.31 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 diff --git a/deployment/go.sum b/deployment/go.sum index afd42327eed..95d2c24c565 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1382,8 +1382,8 @@ github.com/smartcontractkit/chain-selectors v1.0.31 h1:oRHyK88KnsCh4OdU2hr0u70pm github.com/smartcontractkit/chain-selectors v1.0.31/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-20241125151847-c63f5f567fcd h1:hzisF429DPXIXg2yXOHT1Z0TeUcJSO71WN1u03yoeMU= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 h1:A/qi1YCY/9V9i/sthhizZCA0EECAcBfDKeA2w27H5fo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d h1:0tnjo1gpG16PHAouXamgDAAu6e7PWaM0Ppq6dMWnjx0= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= diff --git a/go.mod b/go.mod index f2717f0195a..23cff92f288 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.31 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29ea018b57 diff --git a/go.sum b/go.sum index 650db88dd64..e81a3e2b07d 100644 --- a/go.sum +++ b/go.sum @@ -1076,8 +1076,8 @@ github.com/smartcontractkit/chain-selectors v1.0.31 h1:oRHyK88KnsCh4OdU2hr0u70pm github.com/smartcontractkit/chain-selectors v1.0.31/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-20241125151847-c63f5f567fcd h1:hzisF429DPXIXg2yXOHT1Z0TeUcJSO71WN1u03yoeMU= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 h1:A/qi1YCY/9V9i/sthhizZCA0EECAcBfDKeA2w27H5fo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d h1:0tnjo1gpG16PHAouXamgDAAu6e7PWaM0Ppq6dMWnjx0= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= diff --git a/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go b/integration-tests/contracts/ccipreader_test.go similarity index 58% rename from core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go rename to integration-tests/contracts/ccipreader_test.go index 192bf12f7f5..10363d46da5 100644 --- a/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go +++ b/integration-tests/contracts/ccipreader_test.go @@ -1,8 +1,7 @@ -package ccipreader +package contracts import ( "context" - "encoding/hex" "math/big" "sort" "testing" @@ -18,9 +17,13 @@ import ( "go.uber.org/zap/zapcore" "golang.org/x/exp/maps" + "github.com/smartcontractkit/chainlink-ccip/plugintypes" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/integration-tests/utils/pgtest" + readermocks "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/contractreader" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -34,9 +37,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_reader_tester" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" evmtypes "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" @@ -44,7 +44,6 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/chainlink-ccip/pkg/contractreader" ccipreaderpkg "github.com/smartcontractkit/chainlink-ccip/pkg/reader" - "github.com/smartcontractkit/chainlink-ccip/plugintypes" ) const ( @@ -55,16 +54,10 @@ const ( ) var ( - defaultGasPrice = assets.GWei(10) - InitialLinkPrice = e18Mult(20) - InitialWethPrice = e18Mult(4000) - linkAddress = utils.RandomAddress() - wethAddress = utils.RandomAddress() + defaultGasPrice = assets.GWei(10) ) -func TestCCIPReader_CommitReportsGTETimestamp(t *testing.T) { - ctx := testutils.Context(t) - +func setupGetCommitGTETimestampTest(ctx context.Context, t *testing.T, finalityDepth int64) (*testSetupData, int64, common.Address) { cfg := evmtypes.ChainReaderConfig{ Contracts: map[string]evmtypes.ChainContractReader{ consts.ContractNameOffRamp: { @@ -84,22 +77,29 @@ func TestCCIPReader_CommitReportsGTETimestamp(t *testing.T) { sb, auth := setupSimulatedBackendAndAuth(t) onRampAddress := utils.RandomAddress() - s := testSetup(ctx, t, chainD, chainD, nil, cfg, nil, map[cciptypes.ChainSelector][]types.BoundContract{ - chainS1: { - { - Address: onRampAddress.Hex(), - Name: consts.ContractNameOnRamp, + s := testSetup(ctx, t, testSetupParams{ + ReaderChain: chainD, + DestChain: chainD, + OnChainSeqNums: nil, + Cfg: cfg, + ToMockBindings: map[cciptypes.ChainSelector][]types.BoundContract{ + chainS1: { + { + Address: onRampAddress.Hex(), + Name: consts.ContractNameOnRamp, + }, }, }, - }, - true, - sb, - auth, - ) + BindTester: true, + SimulatedBackend: sb, + Auth: auth, + FinalityDepth: finalityDepth, + }) - tokenA := common.HexToAddress("123") - const numReports = 5 + return s, finalityDepth, onRampAddress +} +func emitCommitReports(ctx context.Context, t *testing.T, s *testSetupData, numReports int, tokenA common.Address, onRampAddress common.Address) uint64 { var firstReportTs uint64 for i := 0; i < numReports; i++ { _, err := s.contract.EmitCommitReportAccepted(s.auth, ccip_reader_tester.OffRampCommitReport{ @@ -145,6 +145,18 @@ func TestCCIPReader_CommitReportsGTETimestamp(t *testing.T) { firstReportTs = b.Time() } } + return firstReportTs +} + +func TestCCIPReader_CommitReportsGTETimestamp(t *testing.T) { + t.Parallel() + ctx := tests.Context(t) + s, _, onRampAddress := setupGetCommitGTETimestampTest(ctx, t, 0) + + tokenA := common.HexToAddress("123") + const numReports = 5 + + firstReportTs := emitCommitReports(ctx, t, s, numReports, tokenA, onRampAddress) // Need to replay as sometimes the logs are not picked up by the log poller (?) // Maybe another situation where chain reader doesn't register filters as expected. @@ -163,7 +175,70 @@ func TestCCIPReader_CommitReportsGTETimestamp(t *testing.T) { ) require.NoError(t, err) return len(reports) == numReports-1 - }, tests.WaitTimeout(t), 50*time.Millisecond) + }, 30*time.Second, 50*time.Millisecond) + + assert.Len(t, reports, numReports-1) + assert.Len(t, reports[0].Report.MerkleRoots, 1) + assert.Equal(t, chainS1, reports[0].Report.MerkleRoots[0].ChainSel) + assert.Equal(t, onRampAddress.Bytes(), []byte(reports[0].Report.MerkleRoots[0].OnRampAddress)) + assert.Equal(t, cciptypes.SeqNum(10), reports[0].Report.MerkleRoots[0].SeqNumsRange.Start()) + assert.Equal(t, cciptypes.SeqNum(20), reports[0].Report.MerkleRoots[0].SeqNumsRange.End()) + assert.Equal(t, "0x0200000000000000000000000000000000000000000000000000000000000000", + reports[0].Report.MerkleRoots[0].MerkleRoot.String()) + assert.Equal(t, tokenA.String(), string(reports[0].Report.PriceUpdates.TokenPriceUpdates[0].TokenID)) + assert.Equal(t, uint64(1000), reports[0].Report.PriceUpdates.TokenPriceUpdates[0].Price.Uint64()) + assert.Equal(t, chainD, reports[0].Report.PriceUpdates.GasPriceUpdates[0].ChainSel) + assert.Equal(t, uint64(90), reports[0].Report.PriceUpdates.GasPriceUpdates[0].GasPrice.Uint64()) +} + +func TestCCIPReader_CommitReportsGTETimestamp_RespectsFinality(t *testing.T) { + t.Parallel() + ctx := tests.Context(t) + var finalityDepth int64 = 10 + s, _, onRampAddress := setupGetCommitGTETimestampTest(ctx, t, finalityDepth) + + tokenA := common.HexToAddress("123") + const numReports = 5 + + firstReportTs := emitCommitReports(ctx, t, s, numReports, tokenA, onRampAddress) + + // Need to replay as sometimes the logs are not picked up by the log poller (?) + // Maybe another situation where chain reader doesn't register filters as expected. + require.NoError(t, s.lp.Replay(ctx, 1)) + + var reports []plugintypes.CommitPluginReportWithMeta + var err error + // Will not return any reports as the finality depth is not reached. + require.Never(t, func() bool { + reports, err = s.reader.CommitReportsGTETimestamp( + ctx, + chainD, + // Skips first report + //nolint:gosec // this won't overflow + time.Unix(int64(firstReportTs)+1, 0), + 10, + ) + require.NoError(t, err) + return len(reports) == numReports-1 + }, 20*time.Second, 50*time.Millisecond) + + // Commit finality depth number of blocks. + for i := 0; i < int(finalityDepth); i++ { + s.sb.Commit() + } + + require.Eventually(t, func() bool { + reports, err = s.reader.CommitReportsGTETimestamp( + ctx, + chainD, + // Skips first report + //nolint:gosec // this won't overflow + time.Unix(int64(firstReportTs)+1, 0), + 10, + ) + require.NoError(t, err) + return len(reports) == numReports-1 + }, 30*time.Second, 50*time.Millisecond) assert.Len(t, reports, numReports-1) assert.Len(t, reports[0].Report.MerkleRoots, 1) @@ -180,7 +255,8 @@ func TestCCIPReader_CommitReportsGTETimestamp(t *testing.T) { } func TestCCIPReader_ExecutedMessageRanges(t *testing.T) { - ctx := testutils.Context(t) + t.Parallel() + ctx := tests.Context(t) cfg := evmtypes.ChainReaderConfig{ Contracts: map[string]evmtypes.ChainContractReader{ consts.ContractNameOffRamp: { @@ -210,8 +286,17 @@ func TestCCIPReader_ExecutedMessageRanges(t *testing.T) { } sb, auth := setupSimulatedBackendAndAuth(t) - s := testSetup(ctx, t, chainD, chainD, nil, cfg, nil, nil, true, sb, auth) - + s := testSetup(ctx, t, testSetupParams{ + ReaderChain: chainD, + DestChain: chainD, + OnChainSeqNums: nil, + Cfg: cfg, + ToBindContracts: nil, + ToMockBindings: nil, + BindTester: true, + SimulatedBackend: sb, + Auth: auth, + }) _, err := s.contract.EmitExecutionStateChanged( s.auth, uint64(chainS1), @@ -252,7 +337,7 @@ func TestCCIPReader_ExecutedMessageRanges(t *testing.T) { ) require.NoError(t, err) return len(executedRanges) == 2 - }, testutils.WaitTimeout(t), 50*time.Millisecond) + }, tests.WaitTimeout(t), 50*time.Millisecond) assert.Equal(t, cciptypes.SeqNum(14), executedRanges[0].Start()) assert.Equal(t, cciptypes.SeqNum(14), executedRanges[0].End()) @@ -262,7 +347,8 @@ func TestCCIPReader_ExecutedMessageRanges(t *testing.T) { } func TestCCIPReader_MsgsBetweenSeqNums(t *testing.T) { - ctx := testutils.Context(t) + t.Parallel() + ctx := tests.Context(t) cfg := evmtypes.ChainReaderConfig{ Contracts: map[string]evmtypes.ChainContractReader{ @@ -296,7 +382,17 @@ func TestCCIPReader_MsgsBetweenSeqNums(t *testing.T) { } sb, auth := setupSimulatedBackendAndAuth(t) - s := testSetup(ctx, t, chainS1, chainD, nil, cfg, nil, nil, true, sb, auth) + s := testSetup(ctx, t, testSetupParams{ + ReaderChain: chainS1, + DestChain: chainD, + OnChainSeqNums: nil, + Cfg: cfg, + ToBindContracts: nil, + ToMockBindings: nil, + BindTester: true, + SimulatedBackend: sb, + Auth: auth, + }) _, err := s.contract.EmitCCIPMessageSent(s.auth, uint64(chainD), ccip_reader_tester.InternalEVM2AnyRampMessage{ Header: ccip_reader_tester.InternalRampMessageHeader{ @@ -375,7 +471,8 @@ func TestCCIPReader_MsgsBetweenSeqNums(t *testing.T) { } func TestCCIPReader_NextSeqNum(t *testing.T) { - ctx := testutils.Context(t) + t.Parallel() + ctx := tests.Context(t) onChainSeqNums := map[cciptypes.ChainSelector]cciptypes.SeqNum{ chainS1: 10, @@ -398,7 +495,17 @@ func TestCCIPReader_NextSeqNum(t *testing.T) { } sb, auth := setupSimulatedBackendAndAuth(t) - s := testSetup(ctx, t, chainD, chainD, onChainSeqNums, cfg, nil, nil, true, sb, auth) + s := testSetup(ctx, t, testSetupParams{ + ReaderChain: chainD, + DestChain: chainD, + OnChainSeqNums: onChainSeqNums, + Cfg: cfg, + ToBindContracts: nil, + ToMockBindings: nil, + BindTester: true, + SimulatedBackend: sb, + Auth: auth, + }) seqNums, err := s.reader.NextSeqNum(ctx, []cciptypes.ChainSelector{chainS1, chainS2, chainS3}) assert.NoError(t, err) @@ -409,44 +516,51 @@ func TestCCIPReader_NextSeqNum(t *testing.T) { } func TestCCIPReader_GetExpectedNextSequenceNumber(t *testing.T) { - ctx := testutils.Context(t) + t.Parallel() + ctx := tests.Context(t) + //env := NewMemoryEnvironmentContractsOnly(t, logger.TestLogger(t), 2, 4, nil) + env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, nil) + state, err := changeset.LoadOnchainState(env.Env) + require.NoError(t, err) - cfg := evmtypes.ChainReaderConfig{ - Contracts: map[string]evmtypes.ChainContractReader{ - consts.ContractNameOnRamp: { - ContractABI: ccip_reader_tester.CCIPReaderTesterABI, - Configs: map[string]*evmtypes.ChainReaderDefinition{ - consts.MethodNameGetExpectedNextSequenceNumber: { - ChainSpecificName: "getExpectedNextSequenceNumber", - ReadType: evmtypes.Method, - }, + selectors := env.Env.AllChainSelectors() + destChain, srcChain := selectors[0], selectors[1] + + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, destChain, srcChain, false)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, srcChain, destChain, false)) + + reader := testSetupRealContracts( + ctx, + t, + destChain, + map[cciptypes.ChainSelector][]types.BoundContract{ + cciptypes.ChainSelector(srcChain): { + { + Address: state.Chains[srcChain].OnRamp.Address().String(), + Name: consts.ContractNameOnRamp, }, }, }, - } - - sb, auth := setupSimulatedBackendAndAuth(t) - s := testSetup(ctx, t, chainS1, chainD, nil, cfg, nil, nil, true, sb, auth) - - _, err := s.contract.SetDestChainSeqNr(s.auth, uint64(chainD), 10) - require.NoError(t, err) - s.sb.Commit() - - seqNum, err := s.reader.GetExpectedNextSequenceNumber(ctx, chainS1, chainD) - require.NoError(t, err) - require.Equal(t, cciptypes.SeqNum(10)+1, seqNum) - - _, err = s.contract.SetDestChainSeqNr(s.auth, uint64(chainD), 25) - require.NoError(t, err) - s.sb.Commit() + nil, + env, + ) - seqNum, err = s.reader.GetExpectedNextSequenceNumber(ctx, chainS1, chainD) - require.NoError(t, err) - require.Equal(t, cciptypes.SeqNum(25)+1, seqNum) + maxExpectedSeqNum := uint64(10) + var i uint64 + for i = 1; i < maxExpectedSeqNum; i++ { + msg := changeset.DefaultRouterMessage(state.Chains[destChain].Receiver.Address()) + msgSentEvent := changeset.TestSendRequest(t, env.Env, state, srcChain, destChain, false, msg) + require.Equal(t, uint64(i), msgSentEvent.SequenceNumber) + require.Equal(t, uint64(i), msgSentEvent.Message.Header.Nonce) // check outbound nonce incremented + seqNum, err2 := reader.GetExpectedNextSequenceNumber(ctx, cs(srcChain), cs(destChain)) + require.NoError(t, err2) + require.Equal(t, cciptypes.SeqNum(i+1), seqNum) + } } func TestCCIPReader_Nonces(t *testing.T) { - ctx := testutils.Context(t) + t.Parallel() + ctx := tests.Context(t) var nonces = map[cciptypes.ChainSelector]map[common.Address]uint64{ chainS1: { utils.RandomAddress(): 10, @@ -477,7 +591,14 @@ func TestCCIPReader_Nonces(t *testing.T) { } sb, auth := setupSimulatedBackendAndAuth(t) - s := testSetup(ctx, t, chainD, chainD, nil, cfg, nil, nil, true, sb, auth) + s := testSetup(ctx, t, testSetupParams{ + ReaderChain: chainD, + DestChain: chainD, + Cfg: cfg, + BindTester: true, + SimulatedBackend: sb, + Auth: auth, + }) // Add some nonces. for chain, addrs := range nonces { @@ -505,113 +626,147 @@ func TestCCIPReader_Nonces(t *testing.T) { } func Test_GetChainFeePriceUpdates(t *testing.T) { - ctx := testutils.Context(t) - sb, auth := setupSimulatedBackendAndAuth(t) - feeQuoter := deployFeeQuoterWithPrices(t, auth, sb, chainS1) + t.Parallel() + ctx := tests.Context(t) + env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, nil) + state, err := changeset.LoadOnchainState(env.Env) + require.NoError(t, err) + + selectors := env.Env.AllChainSelectors() + chain1, chain2 := selectors[0], selectors[1] - s := testSetup(ctx, t, chainD, chainD, nil, evmconfig.DestReaderConfig, + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain1, chain2, false)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain2, chain1, false)) + + // Change the gas price for chain2 + feeQuoter := state.Chains[chain1].FeeQuoter + _, err = feeQuoter.UpdatePrices( + env.Env.Chains[chain1].DeployerKey, fee_quoter.InternalPriceUpdates{ + GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ + { + DestChainSelector: chain2, + UsdPerUnitGas: defaultGasPrice.ToInt(), + }, + }, + }, + ) + require.NoError(t, err) + be := env.Env.Chains[chain1].Client.(*memory.Backend) + be.Commit() + + gas, err := feeQuoter.GetDestinationChainGasPrice(&bind.CallOpts{}, chain2) + require.NoError(t, err) + require.Equal(t, defaultGasPrice.ToInt(), gas.Value) + + reader := testSetupRealContracts( + ctx, + t, + chain1, + //evmconfig.DestReaderConfig, map[cciptypes.ChainSelector][]types.BoundContract{ - chainD: { + cciptypes.ChainSelector(chain1): { { - Address: feeQuoter.Address().String(), + Address: state.Chains[chain1].FeeQuoter.Address().String(), Name: consts.ContractNameFeeQuoter, }, }, }, nil, - false, - sb, - auth, + env, ) - updates := s.reader.GetChainFeePriceUpdate(ctx, []cciptypes.ChainSelector{chainS1, chainS2}) - // only chainS1 has a bound contract + updates := reader.GetChainFeePriceUpdate(ctx, []cciptypes.ChainSelector{cs(chain1), cs(chain2)}) + // only chain1 has a bound contract require.Len(t, updates, 1) - require.Equal(t, defaultGasPrice.ToInt(), updates[chainS1].Value.Int) + require.Equal(t, defaultGasPrice.ToInt(), updates[cs(chain2)].Value.Int) } func Test_LinkPriceUSD(t *testing.T) { - ctx := testutils.Context(t) - sb, auth := setupSimulatedBackendAndAuth(t) - feeQuoter := deployFeeQuoterWithPrices(t, auth, sb, chainS1) + t.Parallel() + ctx := tests.Context(t) + env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, nil) + state, err := changeset.LoadOnchainState(env.Env) + require.NoError(t, err) + + selectors := env.Env.AllChainSelectors() + chain1, chain2 := selectors[0], selectors[1] - s := testSetup(ctx, t, chainD, chainD, nil, evmconfig.DestReaderConfig, + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain1, chain2, false)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain2, chain1, false)) + + reader := testSetupRealContracts( + ctx, + t, + chain1, map[cciptypes.ChainSelector][]types.BoundContract{ - chainD: { + cciptypes.ChainSelector(chain1): { { - Address: feeQuoter.Address().String(), + Address: state.Chains[chain1].FeeQuoter.Address().String(), Name: consts.ContractNameFeeQuoter, }, }, }, nil, - false, - sb, - auth, + env, ) - linkPriceUSD, err := s.reader.LinkPriceUSD(ctx) + linkPriceUSD, err := reader.LinkPriceUSD(ctx) require.NoError(t, err) require.NotNil(t, linkPriceUSD.Int) - require.Equal(t, InitialLinkPrice, linkPriceUSD.Int) + require.Equal(t, changeset.DefaultInitialPrices.LinkPrice, linkPriceUSD.Int) } func Test_GetMedianDataAvailabilityGasConfig(t *testing.T) { - ctx := testutils.Context(t) + t.Parallel() + ctx := tests.Context(t) + env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 4, 4, nil) + state, err := changeset.LoadOnchainState(env.Env) + require.NoError(t, err) - sb, auth := setupSimulatedBackendAndAuth(t) + selectors := env.Env.AllChainSelectors() + destChain, chain1, chain2, chain3 := selectors[0], selectors[1], selectors[2], selectors[3] - // All fee quoters using same auth and simulated backend for simplicity - feeQuoter1 := deployFeeQuoterWithPrices(t, auth, sb, chainD) - feeQuoter2 := deployFeeQuoterWithPrices(t, auth, sb, chainD) - feeQuoter3 := deployFeeQuoterWithPrices(t, auth, sb, chainD) - feeQuoters := []*fee_quoter.FeeQuoter{feeQuoter1, feeQuoter2, feeQuoter3} + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain1, destChain, false)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain2, destChain, false)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain3, destChain, false)) - // Update the dest chain config for each fee quoter - for i, fq := range feeQuoters { - destChainCfg := defaultFeeQuoterDestChainConfig() + boundContracts := map[cciptypes.ChainSelector][]types.BoundContract{} + for i, selector := range env.Env.AllChainSelectorsExcluding([]uint64{destChain}) { + feeQuoter := state.Chains[selector].FeeQuoter + destChainCfg := changeset.DefaultFeeQuoterDestChainConfig() //nolint:gosec // disable G115 destChainCfg.DestDataAvailabilityOverheadGas = uint32(100 + i) //nolint:gosec // disable G115 destChainCfg.DestGasPerDataAvailabilityByte = uint16(200 + i) //nolint:gosec // disable G115 destChainCfg.DestDataAvailabilityMultiplierBps = uint16(1 + i) - _, err := fq.ApplyDestChainConfigUpdates(auth, []fee_quoter.FeeQuoterDestChainConfigArgs{ + _, err2 := feeQuoter.ApplyDestChainConfigUpdates(env.Env.Chains[selector].DeployerKey, []fee_quoter.FeeQuoterDestChainConfigArgs{ { - DestChainSelector: uint64(chainD), + DestChainSelector: destChain, DestChainConfig: destChainCfg, }, }) - sb.Commit() - require.NoError(t, err) - } - - s := testSetup(ctx, t, chainD, chainD, nil, evmconfig.DestReaderConfig, map[cciptypes.ChainSelector][]types.BoundContract{ - chainS1: { - { - Address: feeQuoter1.Address().String(), - Name: consts.ContractNameFeeQuoter, - }, - }, - chainS2: { - { - Address: feeQuoter2.Address().String(), - Name: consts.ContractNameFeeQuoter, - }, - }, - chainS3: { + require.NoError(t, err2) + be := env.Env.Chains[selector].Client.(*memory.Backend) + be.Commit() + boundContracts[cs(selector)] = []types.BoundContract{ { - Address: feeQuoter3.Address().String(), + Address: feeQuoter.Address().String(), Name: consts.ContractNameFeeQuoter, }, - }, - }, nil, - false, - sb, - auth, + } + } + + reader := testSetupRealContracts( + ctx, + t, + destChain, + boundContracts, + nil, + env, ) - daConfig, err := s.reader.GetMedianDataAvailabilityGasConfig(ctx) + daConfig, err := reader.GetMedianDataAvailabilityGasConfig(ctx) require.NoError(t, err) // Verify the results @@ -621,140 +776,43 @@ func Test_GetMedianDataAvailabilityGasConfig(t *testing.T) { } func Test_GetWrappedNativeTokenPriceUSD(t *testing.T) { - ctx := testutils.Context(t) - sb, auth := setupSimulatedBackendAndAuth(t) - feeQuoter := deployFeeQuoterWithPrices(t, auth, sb, chainS1) + t.Parallel() + ctx := tests.Context(t) + env := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, nil) + state, err := changeset.LoadOnchainState(env.Env) + require.NoError(t, err) + + selectors := env.Env.AllChainSelectors() + chain1, chain2 := selectors[0], selectors[1] - // Mock the routerContract to return a native token address - routerContract := deployRouterWithNativeToken(t, auth, sb) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain1, chain2, false)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(env.Env, state, chain2, chain1, false)) - s := testSetup(ctx, t, chainD, chainD, nil, evmconfig.DestReaderConfig, + reader := testSetupRealContracts( + ctx, + t, + chain1, map[cciptypes.ChainSelector][]types.BoundContract{ - chainD: { + cciptypes.ChainSelector(chain1): { { - Address: feeQuoter.Address().String(), + Address: state.Chains[chain1].FeeQuoter.Address().String(), Name: consts.ContractNameFeeQuoter, }, { - Address: routerContract.Address().String(), + Address: state.Chains[chain1].Router.Address().String(), Name: consts.ContractNameRouter, }, }, }, nil, - false, - sb, - auth, + env, ) - prices := s.reader.GetWrappedNativeTokenPriceUSD(ctx, []cciptypes.ChainSelector{chainD, chainS1}) + prices := reader.GetWrappedNativeTokenPriceUSD(ctx, []cciptypes.ChainSelector{cciptypes.ChainSelector(chain1), cciptypes.ChainSelector(chain2)}) // Only chainD has reader contracts bound require.Len(t, prices, 1) - require.Equal(t, InitialWethPrice, prices[chainD].Int) -} - -func deployRouterWithNativeToken(t *testing.T, auth *bind.TransactOpts, sb *simulated.Backend) *router.Router { - address, _, _, err := router.DeployRouter( - auth, - sb.Client(), - wethAddress, - utils.RandomAddress(), // armProxy address - ) - require.NoError(t, err) - sb.Commit() - - routerContract, err := router.NewRouter(address, sb.Client()) - require.NoError(t, err) - - return routerContract -} - -func deployFeeQuoterWithPrices(t *testing.T, auth *bind.TransactOpts, sb *simulated.Backend, destChain cciptypes.ChainSelector) *fee_quoter.FeeQuoter { - address, _, _, err := fee_quoter.DeployFeeQuoter( - auth, - sb.Client(), - fee_quoter.FeeQuoterStaticConfig{ - MaxFeeJuelsPerMsg: big.NewInt(0).Mul(big.NewInt(2e2), big.NewInt(1e18)), - LinkToken: linkAddress, - TokenPriceStalenessThreshold: uint32(24 * 60 * 60), - }, - []common.Address{auth.From}, - []common.Address{wethAddress, linkAddress}, - []fee_quoter.FeeQuoterTokenPriceFeedUpdate{}, - []fee_quoter.FeeQuoterTokenTransferFeeConfigArgs{}, - []fee_quoter.FeeQuoterPremiumMultiplierWeiPerEthArgs{}, - []fee_quoter.FeeQuoterDestChainConfigArgs{ - { - - DestChainSelector: uint64(destChain), - DestChainConfig: defaultFeeQuoterDestChainConfig(), - }, - }, - ) - - require.NoError(t, err) - sb.Commit() - - feeQuoter, err := fee_quoter.NewFeeQuoter(address, sb.Client()) - require.NoError(t, err) - - _, err = feeQuoter.UpdatePrices( - auth, fee_quoter.InternalPriceUpdates{ - GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ - { - DestChainSelector: uint64(chainS1), - UsdPerUnitGas: defaultGasPrice.ToInt(), - }, - }, - TokenPriceUpdates: []fee_quoter.InternalTokenPriceUpdate{ - { - SourceToken: linkAddress, - UsdPerToken: InitialLinkPrice, - }, - { - SourceToken: wethAddress, - UsdPerToken: InitialWethPrice, - }, - }, - }, - ) - require.NoError(t, err) - sb.Commit() - - gas, err := feeQuoter.GetDestinationChainGasPrice(&bind.CallOpts{}, uint64(chainS1)) - require.NoError(t, err) - require.Equal(t, defaultGasPrice.ToInt(), gas.Value) - - return feeQuoter -} - -func defaultFeeQuoterDestChainConfig() fee_quoter.FeeQuoterDestChainConfig { - // https://github.com/smartcontractkit/ccip/blob/c4856b64bd766f1ddbaf5d13b42d3c4b12efde3a/contracts/src/v0.8/ccip/libraries/Internal.sol#L337-L337 - /* - ```Solidity - // bytes4(keccak256("CCIP ChainFamilySelector EVM")) - bytes4 public constant CHAIN_FAMILY_SELECTOR_EVM = 0x2812d52c; - ``` - */ - evmFamilySelector, _ := hex.DecodeString("2812d52c") - return fee_quoter.FeeQuoterDestChainConfig{ - IsEnabled: true, - MaxNumberOfTokensPerMsg: 10, - MaxDataBytes: 256, - MaxPerMsgGasLimit: 3_000_000, - DestGasOverhead: 50_000, - DefaultTokenFeeUSDCents: 1, - DestGasPerPayloadByte: 10, - DestDataAvailabilityOverheadGas: 100, - DestGasPerDataAvailabilityByte: 100, - DestDataAvailabilityMultiplierBps: 1, - DefaultTokenDestGasOverhead: 125_000, - DefaultTxGasLimit: 200_000, - GasMultiplierWeiPerEth: 1, - NetworkFeeUSDCents: 1, - ChainFamilySelector: [4]byte(evmFamilySelector), - } + require.Equal(t, changeset.DefaultInitialPrices.WethPrice, prices[cciptypes.ChainSelector(chain1)].Int) } func setupSimulatedBackendAndAuth(t *testing.T) (*simulated.Backend, *bind.TransactOpts) { @@ -774,69 +832,138 @@ func setupSimulatedBackendAndAuth(t *testing.T) (*simulated.Backend, *bind.Trans return simulatedBackend, auth } -func testSetup( +func testSetupRealContracts( ctx context.Context, t *testing.T, - readerChain, - destChain cciptypes.ChainSelector, - onChainSeqNums map[cciptypes.ChainSelector]cciptypes.SeqNum, - cfg evmtypes.ChainReaderConfig, + destChain uint64, toBindContracts map[cciptypes.ChainSelector][]types.BoundContract, toMockBindings map[cciptypes.ChainSelector][]types.BoundContract, - bindTester bool, - simulatedBackend *simulated.Backend, - auth *bind.TransactOpts, + env changeset.DeployedEnv, +) ccipreaderpkg.CCIPReader { + db := pgtest.NewSqlxDB(t) + lpOpts := logpoller.Opts{ + PollPeriod: time.Millisecond, + FinalityDepth: 0, + BackfillBatchSize: 10, + RpcBatchSize: 10, + KeepFinalizedBlocksDepth: 100000, + } + lggr := logger.TestLogger(t) + lggr.SetLogLevel(zapcore.ErrorLevel) + + var crs = make(map[cciptypes.ChainSelector]contractreader.Extended) + for chain, bindings := range toBindContracts { + be := env.Env.Chains[uint64(chain)].Client.(*memory.Backend) + cl := client.NewSimulatedBackendClient(t, be.Sim, big.NewInt(0).SetUint64(uint64(chain))) + headTracker := headtracker.NewSimulatedHeadTracker(cl, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) + lp := logpoller.NewLogPoller(logpoller.NewORM(big.NewInt(0).SetUint64(uint64(chain)), db, lggr), + cl, + lggr, + headTracker, + lpOpts, + ) + require.NoError(t, lp.Start(ctx)) + + var cfg evmtypes.ChainReaderConfig + if chain == cs(destChain) { + cfg = evmconfig.DestReaderConfig + } else { + cfg = evmconfig.SourceReaderConfig + } + cr, err := evm.NewChainReaderService(ctx, lggr, lp, headTracker, cl, cfg) + require.NoError(t, err) + + extendedCr2 := contractreader.NewExtendedContractReader(cr) + err = extendedCr2.Bind(ctx, bindings) + require.NoError(t, err) + crs[cciptypes.ChainSelector(chain)] = extendedCr2 + + err = cr.Start(ctx) + require.NoError(t, err) + + t.Cleanup(func() { + require.NoError(t, cr.Close()) + require.NoError(t, lp.Close()) + require.NoError(t, db.Close()) + }) + } + + for chain, bindings := range toMockBindings { + if _, ok := crs[chain]; ok { + require.False(t, ok, "chain %d already exists", chain) + } + m := readermocks.NewMockContractReaderFacade(t) + m.EXPECT().Bind(ctx, bindings).Return(nil) + ecr := contractreader.NewExtendedContractReader(m) + err := ecr.Bind(ctx, bindings) + require.NoError(t, err) + crs[chain] = ecr + } + + contractReaders := map[cciptypes.ChainSelector]contractreader.Extended{} + for chain, cr := range crs { + contractReaders[chain] = cr + } + contractWriters := make(map[cciptypes.ChainSelector]types.ChainWriter) + reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(ctx, lggr, contractReaders, contractWriters, cciptypes.ChainSelector(destChain), nil) + + return reader +} + +func testSetup( + ctx context.Context, + t *testing.T, + params testSetupParams, ) *testSetupData { - address, _, _, err := ccip_reader_tester.DeployCCIPReaderTester(auth, simulatedBackend.Client()) + address, _, _, err := ccip_reader_tester.DeployCCIPReaderTester(params.Auth, params.SimulatedBackend.Client()) assert.NoError(t, err) - simulatedBackend.Commit() + params.SimulatedBackend.Commit() // Setup contract client - contract, err := ccip_reader_tester.NewCCIPReaderTester(address, simulatedBackend.Client()) + contract, err := ccip_reader_tester.NewCCIPReaderTester(address, params.SimulatedBackend.Client()) assert.NoError(t, err) lggr := logger.TestLogger(t) lggr.SetLogLevel(zapcore.ErrorLevel) db := pgtest.NewSqlxDB(t) - t.Cleanup(func() { assert.NoError(t, db.Close()) }) lpOpts := logpoller.Opts{ PollPeriod: time.Millisecond, - FinalityDepth: 0, + FinalityDepth: params.FinalityDepth, BackfillBatchSize: 10, RpcBatchSize: 10, KeepFinalizedBlocksDepth: 100000, } - cl := client.NewSimulatedBackendClient(t, simulatedBackend, big.NewInt(0).SetUint64(uint64(readerChain))) + cl := client.NewSimulatedBackendClient(t, params.SimulatedBackend, big.NewInt(0).SetUint64(uint64(params.ReaderChain))) headTracker := headtracker.NewSimulatedHeadTracker(cl, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) - lp := logpoller.NewLogPoller(logpoller.NewORM(big.NewInt(0).SetUint64(uint64(readerChain)), db, lggr), + lp := logpoller.NewLogPoller(logpoller.NewORM(big.NewInt(0).SetUint64(uint64(params.ReaderChain)), db, lggr), cl, lggr, headTracker, lpOpts, ) - servicetest.Run(t, lp) + assert.NoError(t, lp.Start(ctx)) - for sourceChain, seqNum := range onChainSeqNums { - _, err1 := contract.SetSourceChainConfig(auth, uint64(sourceChain), ccip_reader_tester.OffRampSourceChainConfig{ + for sourceChain, seqNum := range params.OnChainSeqNums { + _, err1 := contract.SetSourceChainConfig(params.Auth, uint64(sourceChain), ccip_reader_tester.OffRampSourceChainConfig{ IsEnabled: true, MinSeqNr: uint64(seqNum), OnRamp: utils.RandomAddress().Bytes(), }) assert.NoError(t, err1) - simulatedBackend.Commit() + params.SimulatedBackend.Commit() scc, err1 := contract.GetSourceChainConfig(&bind.CallOpts{Context: ctx}, uint64(sourceChain)) assert.NoError(t, err1) assert.Equal(t, seqNum, cciptypes.SeqNum(scc.MinSeqNr)) } - contractNames := maps.Keys(cfg.Contracts) + contractNames := maps.Keys(params.Cfg.Contracts) - cr, err := evm.NewChainReaderService(ctx, lggr, lp, headTracker, cl, cfg) + cr, err := evm.NewChainReaderService(ctx, lggr, lp, headTracker, cl, params.Cfg) require.NoError(t, err) extendedCr := contractreader.NewExtendedContractReader(cr) - if bindTester { + if params.BindTester { err = extendedCr.Bind(ctx, []types.BoundContract{ { Address: address.String(), @@ -847,8 +974,8 @@ func testSetup( } var otherCrs = make(map[cciptypes.ChainSelector]contractreader.Extended) - for chain, bindings := range toBindContracts { - cl2 := client.NewSimulatedBackendClient(t, simulatedBackend, big.NewInt(0).SetUint64(uint64(chain))) + for chain, bindings := range params.ToBindContracts { + cl2 := client.NewSimulatedBackendClient(t, params.SimulatedBackend, big.NewInt(0).SetUint64(uint64(chain))) headTracker2 := headtracker.NewSimulatedHeadTracker(cl2, lpOpts.UseFinalityTag, lpOpts.FinalityDepth) lp2 := logpoller.NewLogPoller(logpoller.NewORM(big.NewInt(0).SetUint64(uint64(chain)), db, lggr), cl2, @@ -856,9 +983,9 @@ func testSetup( headTracker2, lpOpts, ) - servicetest.Run(t, lp2) + require.NoError(t, lp2.Start(ctx)) - cr2, err2 := evm.NewChainReaderService(ctx, lggr, lp2, headTracker2, cl2, cfg) + cr2, err2 := evm.NewChainReaderService(ctx, lggr, lp2, headTracker2, cl2, params.Cfg) require.NoError(t, err2) extendedCr2 := contractreader.NewExtendedContractReader(cr2) @@ -867,7 +994,7 @@ func testSetup( otherCrs[chain] = extendedCr2 } - for chain, bindings := range toMockBindings { + for chain, bindings := range params.ToMockBindings { if _, ok := otherCrs[chain]; ok { require.False(t, ok, "chain %d already exists", chain) } @@ -879,20 +1006,27 @@ func testSetup( otherCrs[chain] = ecr } - servicetest.Run(t, cr) + err = cr.Start(ctx) + require.NoError(t, err) - contractReaders := map[cciptypes.ChainSelector]contractreader.Extended{readerChain: extendedCr} + contractReaders := map[cciptypes.ChainSelector]contractreader.Extended{params.ReaderChain: extendedCr} for chain, cr := range otherCrs { contractReaders[chain] = cr } contractWriters := make(map[cciptypes.ChainSelector]types.ChainWriter) - reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(ctx, lggr, contractReaders, contractWriters, destChain, nil) + reader := ccipreaderpkg.NewCCIPReaderWithExtendedContractReaders(ctx, lggr, contractReaders, contractWriters, params.DestChain, nil) + + t.Cleanup(func() { + require.NoError(t, cr.Close()) + require.NoError(t, lp.Close()) + require.NoError(t, db.Close()) + }) return &testSetupData{ contractAddr: address, contract: contract, - sb: simulatedBackend, - auth: auth, + sb: params.SimulatedBackend, + auth: params.Auth, lp: lp, cl: cl, reader: reader, @@ -900,6 +1034,19 @@ func testSetup( } } +type testSetupParams struct { + ReaderChain cciptypes.ChainSelector + DestChain cciptypes.ChainSelector + OnChainSeqNums map[cciptypes.ChainSelector]cciptypes.SeqNum + Cfg evmtypes.ChainReaderConfig + ToBindContracts map[cciptypes.ChainSelector][]types.BoundContract + ToMockBindings map[cciptypes.ChainSelector][]types.BoundContract + BindTester bool + SimulatedBackend *simulated.Backend + Auth *bind.TransactOpts + FinalityDepth int64 +} + type testSetupData struct { contractAddr common.Address contract *ccip_reader_tester.CCIPReaderTester @@ -911,10 +1058,6 @@ type testSetupData struct { extendedCR contractreader.Extended } -func uBigInt(i uint64) *big.Int { - return new(big.Int).SetUint64(i) -} - -func e18Mult(amount uint64) *big.Int { - return new(big.Int).Mul(uBigInt(amount), uBigInt(1e18)) +func cs(i uint64) cciptypes.ChainSelector { + return cciptypes.ChainSelector(i) } diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 6b1a75f3915..800b7889917 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -37,8 +37,9 @@ require ( github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.31 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d + github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.17 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 @@ -85,6 +86,8 @@ require ( github.com/CosmWasm/wasmd v0.40.1 // indirect github.com/CosmWasm/wasmvm v1.2.4 // indirect github.com/DataDog/zstd v1.5.2 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect github.com/Khan/genqlient v0.7.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -257,6 +260,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/googleapis/gax-go/v2 v2.14.0 // indirect github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect @@ -416,7 +420,6 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29ea018b57 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect - github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749 // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect @@ -506,9 +509,10 @@ require ( golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gonum.org/v1/gonum v0.15.1 // indirect + google.golang.org/api v0.205.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index e02298a79d1..a2e1e94b61d 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -18,10 +18,10 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ= -cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= -cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= +cloud.google.com/go/auth v0.10.1 h1:TnK46qldSfHWt2a0b/hciaiVJsmDXWy9FqyUan0uYiI= +cloud.google.com/go/auth v0.10.1/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= +cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -29,8 +29,8 @@ cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUM cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.28.1 h1:XwPcZjgMCnU2tkwY10VleUjSAfpTj9RDn+kGrbYsi8o= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -115,12 +115,12 @@ github.com/Depado/ginprom v1.8.0 h1:zaaibRLNI1dMiiuj1MKzatm8qrcHzikMlCc1anqOdyo= github.com/Depado/ginprom v1.8.0/go.mod h1:XBaKzeNBqPF4vxJpNLincSQZeMDnZp1tIbU0FU0UKgg= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3 h1:cb3br57K508pQEFgBxn9GDhPS9HefpyMPK1RzmtMNzk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.3 h1:xir5X8TS8UBVPWg2jHL+cSTf0jZgqYQSA54TscSt1/0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.3/go.mod h1:SsdWig2J5PMnfMvfJuEb1uZa8Y+kvNyvrULFo69gTFk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.3 h1:2vcVkrNdSMJpoOVAWi9ApsQR5iqNeFGt5Qx8Xlt3IoI= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.3/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Khan/genqlient v0.7.0 h1:GZ1meyRnzcDTK48EjqB8t3bcfYvHArCUUvgOwpz1D4w= @@ -743,8 +743,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= @@ -753,12 +753,12 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/gophercloud/gophercloud v1.13.0 h1:8iY9d1DAbzMW6Vok1AxbbK5ZaUjzMp0tdyt4fX9IeJ0= github.com/gophercloud/gophercloud v1.13.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -1403,8 +1403,8 @@ github.com/smartcontractkit/chain-selectors v1.0.31 h1:oRHyK88KnsCh4OdU2hr0u70pm github.com/smartcontractkit/chain-selectors v1.0.31/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-20241125151847-c63f5f567fcd h1:hzisF429DPXIXg2yXOHT1Z0TeUcJSO71WN1u03yoeMU= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 h1:A/qi1YCY/9V9i/sthhizZCA0EECAcBfDKeA2w27H5fo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d h1:0tnjo1gpG16PHAouXamgDAAu6e7PWaM0Ppq6dMWnjx0= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= @@ -2063,8 +2063,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.202.0 h1:y1iuVHMqokQbimW79ZqPZWo4CiyFu6HcCYHwSNyzlfo= -google.golang.org/api v0.202.0/go.mod h1:3Jjeq7M/SFblTNCp7ES2xhq+WvGL0KeXI0joHQBfwTQ= +google.golang.org/api v0.205.0 h1:LFaxkAIpDb/GsrWV20dMMo5MR0h8UARTbn24LmD+0Pg= +google.golang.org/api v0.205.0/go.mod h1:NrK1EMqO8Xk6l6QwRAmrXXg2v6dzukhlOyvkYtnvUuc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2111,10 +2111,10 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw= -google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 3b6f301c1a7..b9f646a4c9d 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -21,7 +21,7 @@ require ( github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.17 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.9 github.com/smartcontractkit/chainlink-testing-framework/wasp v1.50.2 - github.com/smartcontractkit/chainlink/deployment v0.0.0-00010101000000-000000000000 + github.com/smartcontractkit/chainlink/deployment v0.0.0-20241120141814-47da13e86197 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20241030133659-9ec788e78b4f github.com/smartcontractkit/chainlink/v2 v2.14.0-mercury-20240807.0.20241120195829-bd7a1943ad07 github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20241009055228-33d0c0bf38de @@ -399,7 +399,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/chain-selectors v1.0.31 // indirect github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29ea018b57 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect @@ -505,8 +505,8 @@ require ( gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 3469e4bc908..0ae4bee7333 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -115,12 +115,12 @@ github.com/Depado/ginprom v1.8.0 h1:zaaibRLNI1dMiiuj1MKzatm8qrcHzikMlCc1anqOdyo= github.com/Depado/ginprom v1.8.0/go.mod h1:XBaKzeNBqPF4vxJpNLincSQZeMDnZp1tIbU0FU0UKgg= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3 h1:cb3br57K508pQEFgBxn9GDhPS9HefpyMPK1RzmtMNzk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.3/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.3 h1:xir5X8TS8UBVPWg2jHL+cSTf0jZgqYQSA54TscSt1/0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.3/go.mod h1:SsdWig2J5PMnfMvfJuEb1uZa8Y+kvNyvrULFo69gTFk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.3 h1:2vcVkrNdSMJpoOVAWi9ApsQR5iqNeFGt5Qx8Xlt3IoI= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.3/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/K-Phoen/grabana v0.22.2 h1:tMiSvcKHnDbXi3IgBCax2+sg5qL6x0G6wMURHgjGDag= @@ -751,8 +751,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/gophercloud/gophercloud v1.13.0 h1:8iY9d1DAbzMW6Vok1AxbbK5ZaUjzMp0tdyt4fX9IeJ0= github.com/gophercloud/gophercloud v1.13.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -1388,12 +1388,14 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/slack-go/slack v0.15.0 h1:LE2lj2y9vqqiOf+qIIy0GvEoxgF1N5yLGZffmEZykt0= github.com/slack-go/slack v0.15.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 h1:qQH6fZZe31nBAG6INHph3z5ysDTPptyu0TR9uoJ1+ok= +github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86/go.mod h1:WtWOoVQQEHxRHL2hNmuRrvDfYfQG/CioFNoa9Rr2mBE= github.com/smartcontractkit/chain-selectors v1.0.31 h1:oRHyK88KnsCh4OdU2hr0u70pm3KUgyMDyK0v0aOtUk4= github.com/smartcontractkit/chain-selectors v1.0.31/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-20241125151847-c63f5f567fcd h1:hzisF429DPXIXg2yXOHT1Z0TeUcJSO71WN1u03yoeMU= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6 h1:A/qi1YCY/9V9i/sthhizZCA0EECAcBfDKeA2w27H5fo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241128080738-06bef8620ac6/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d h1:0tnjo1gpG16PHAouXamgDAAu6e7PWaM0Ppq6dMWnjx0= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d/go.mod h1:ny87uTW6hLjCTLiBqBRNFEhETSXhHWevYlPclT5lSco= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= @@ -1402,6 +1404,8 @@ github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29e github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29ea018b57/go.mod h1:QPiorgpbLv4+Jn4YO6xxU4ftTu4T3QN8HwX3ImP59DE= github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6AnNt+Wg64sVG+XSA49c= github.com/smartcontractkit/chainlink-feeds v0.1.1/go.mod h1:55EZ94HlKCfAsUiKUTNI7QlE/3d3IwTlsU3YNa/nBb4= +github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 h1:0ewLMbAz3rZrovdRUCgd028yOXX8KigB4FndAUdI2kM= +github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0/go.mod h1:/dVVLXrsp+V0AbcYGJo3XMzKg3CkELsweA/TTopCsKE= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 h1:onBe3DqNrbtOAzKS4PrPIiJX65BGo1aYiYZxFVEW+jc= github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749 h1:gkrjGJAtbKMOliJPaZ73EyJmO8AyDVi80+PEJocRMn4= @@ -2048,8 +2052,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.202.0 h1:y1iuVHMqokQbimW79ZqPZWo4CiyFu6HcCYHwSNyzlfo= -google.golang.org/api v0.202.0/go.mod h1:3Jjeq7M/SFblTNCp7ES2xhq+WvGL0KeXI0joHQBfwTQ= +google.golang.org/api v0.205.0 h1:LFaxkAIpDb/GsrWV20dMMo5MR0h8UARTbn24LmD+0Pg= +google.golang.org/api v0.205.0/go.mod h1:NrK1EMqO8Xk6l6QwRAmrXXg2v6dzukhlOyvkYtnvUuc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2096,10 +2100,10 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw= -google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= diff --git a/integration-tests/smoke/ccip/ccip_batching_test.go b/integration-tests/smoke/ccip/ccip_batching_test.go index 864e01c2007..9c85ec95c21 100644 --- a/integration-tests/smoke/ccip/ccip_batching_test.go +++ b/integration-tests/smoke/ccip/ccip_batching_test.go @@ -117,6 +117,7 @@ func Test_CCIPBatching(t *testing.T) { }) t.Run("batch data only messages from multiple sources", func(t *testing.T) { + t.Skipf("skipping - failing consistently in CI") var ( wg sync.WaitGroup sourceChains = []uint64{sourceChain1, sourceChain2} diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go new file mode 100644 index 00000000000..2b14c883238 --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -0,0 +1,317 @@ +package smoke + +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/test-go/testify/require" + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" + + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var ( + linkPrice = deployment.E18Mult(100) + wethPrice = deployment.E18Mult(4000) +) + +func Test_CCIPFeeBoosting(t *testing.T) { + e := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, &changeset.TestConfigs{ + OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + // Only 1 boost (=OCR round) is enough to cover the fee + params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 + // Disable token price updates + params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + // Disable gas price updates + params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + // Disable token price updates + params.CommitOffChainConfig.TokenInfo = nil + return params + }, + }) + + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + + allChainSelectors := maps.Keys(e.Env.Chains) + require.Len(t, allChainSelectors, 2) + sourceChain := allChainSelectors[0] + destChain := allChainSelectors[1] + t.Log("All chain selectors:", allChainSelectors, + ", home chain selector:", e.HomeChainSel, + ", feed chain selector:", e.FeedChainSel, + ", source chain selector:", sourceChain, + ", dest chain selector:", destChain, + ) + + fetchedGasPriceDest, err := e.Env.Chains[destChain].Client.SuggestGasPrice(tests.Context(t)) + require.NoError(t, err) + originalGasPriceDestUSD := new(big.Int).Div( + new(big.Int).Mul(fetchedGasPriceDest, wethPrice), + big.NewInt(1e18), + ) + t.Log("Gas price on dest chain (USD):", originalGasPriceDestUSD) + + // Adjust destination gas price on source fee quoter to 95% of the current value + adjustedGasPriceDest := + new(big.Int).Div( + new(big.Int).Mul(originalGasPriceDestUSD, big.NewInt(95)), + big.NewInt(100), + ) + t.Log("Adjusted gas price on dest chain:", adjustedGasPriceDest) + + initialPrices := changeset.InitialPrices{ + LinkPrice: linkPrice, + WethPrice: wethPrice, + GasPrice: changeset.ToPackedFee(adjustedGasPriceDest, big.NewInt(0)), + } + + laneCfg := changeset.LaneConfig{ + SourceSelector: sourceChain, + DestSelector: destChain, + InitialPricesBySource: initialPrices, + FeeQuoterDestChain: changeset.DefaultFeeQuoterDestChainConfig(), + } + require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) + + // Update token prices in destination chain FeeQuoter + err = updateTokensPrices(e, state, destChain, map[common.Address]*big.Int{ + state.Chains[destChain].LinkToken.Address(): linkPrice, + state.Chains[destChain].Weth9.Address(): wethPrice, + }) + require.NoError(t, err) + + startBlocks := make(map[uint64]*uint64) + expectedSeqNum := make(map[changeset.SourceDestPair]uint64) + expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) + + latesthdr, err := e.Env.Chains[sourceChain].Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + msgSentEvent := changeset.TestSendRequest(t, e.Env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + Data: []byte("message that needs fee boosting"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + startBlocks[sourceChain] = &block + expectedSeqNum[changeset.SourceDestPair{ + SourceChainSelector: sourceChain, + DestChainSelector: destChain, + }] = msgSentEvent.SequenceNumber + expectedSeqNumExec[changeset.SourceDestPair{ + SourceChainSelector: sourceChain, + DestChainSelector: destChain, + }] = []uint64{msgSentEvent.SequenceNumber} + + err = updateGasPrice(e, state, sourceChain, destChain, originalGasPriceDestUSD) + require.NoError(t, err) + + // Confirm gas prices are updated + srcFeeQuoter := state.Chains[sourceChain].FeeQuoter + err = changeset.ConfirmGasPriceUpdated(t, e.Env.Chains[destChain], srcFeeQuoter, 0, originalGasPriceDestUSD) + require.NoError(t, err) + + // Confirm that fee boosting will be triggered + require.True(t, willTriggerFeeBoosting(t, msgSentEvent, state, sourceChain, destChain)) + + // hack + time.Sleep(30 * time.Second) + replayBlocks := make(map[uint64]uint64) + replayBlocks[sourceChain] = 1 + replayBlocks[destChain] = 1 + changeset.ReplayLogs(t, e.Env.Offchain, replayBlocks) + + // Confirm that the message is committed and executed + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, e.Env, state, expectedSeqNum, startBlocks) + changeset.ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) +} + +// TODO: Find a more accurate way to determine if fee boosting will be triggered +func willTriggerFeeBoosting( + t *testing.T, + msgSentEvent *onramp.OnRampCCIPMessageSent, + state changeset.CCIPOnChainState, + srcChain, destChain uint64) bool { + msg := convertToMessage(msgSentEvent.Message) + t.Log("\n=== Fee Boosting Analysis ===") + t.Logf("Src Chain: %d", msg.Header.SourceChainSelector) + t.Logf("Dest Chain: %d", msg.Header.DestChainSelector) + + ep := ccipevm.NewGasEstimateProvider() + chainState, exists := state.Chains[srcChain] + require.True(t, exists) + feeQuoter := chainState.FeeQuoter + + premium, err := feeQuoter.GetPremiumMultiplierWeiPerEth(&bind.CallOpts{Context: context.Background()}, chainState.Weth9.Address()) + require.NoError(t, err) + t.Logf("Premium: %d", premium) + + // Get LINK price + linkPrice, err := feeQuoter.GetTokenPrice(&bind.CallOpts{Context: context.Background()}, chainState.LinkToken.Address()) + require.NoError(t, err) + t.Logf("LINK Price: %s", linkPrice.Value.String()) + t.Logf("Juels in message: %s", msg.FeeValueJuels.String()) + + // Calculate fee in USD token + fee := new(big.Int).Div( + new(big.Int).Mul(linkPrice.Value, msg.FeeValueJuels.Int), + new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil), + ) + t.Logf("Fee paid (in USD token): %s", fee.String()) + + // Calculate message gas + messageGas := new(big.Int).SetUint64(ep.CalculateMessageMaxGas(msg)) + t.Logf("Estimated message gas: %s", messageGas.String()) + + // Get token and gas prices + nativeTokenAddress := chainState.Weth9.Address() + tokenAndGasPrice, err := feeQuoter.GetTokenAndGasPrices(&bind.CallOpts{Context: context.Background()}, nativeTokenAddress, destChain) + require.NoError(t, err) + t.Logf("Raw gas price (uint224): %s for chain: %d", tokenAndGasPrice.GasPriceValue.String(), destChain) + + // Extract uint112 gas price + gasPrice, err := convertGasPriceToUint112(tokenAndGasPrice.GasPriceValue) + require.NoError(t, err) + t.Logf("Extracted gas price (uint112): %s", gasPrice.String()) + t.Logf("Native token price: %s", tokenAndGasPrice.TokenPrice.String()) + + // Calculate total execution cost + execCost := new(big.Int).Mul(messageGas, gasPrice) + t.Logf("Total execution cost: %s", execCost.String()) + + // Check if fee boosting will trigger + willBoost := execCost.Cmp(fee) > 0 + t.Logf("\nWill fee boosting trigger? %v", willBoost) + t.Logf("Execution cost / Fee ratio: %.2f", + new(big.Float).Quo( + new(big.Float).SetInt(execCost), + new(big.Float).SetInt(fee), + ), + ) + + return execCost.Cmp(fee) > 0 +} + +func convertGasPriceToUint112(gasPrice *big.Int) (*big.Int, error) { + // Create a mask for uint112 (112 bits of 1s) + mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 112), big.NewInt(1)) + + // Extract the lower 112 bits using AND operation + result := new(big.Int).And(gasPrice, mask) + + return result, nil +} + +func convertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { + // Convert header + header := cciptypes.RampMessageHeader{ + MessageID: cciptypes.Bytes32(msg.Header.MessageId), + SourceChainSelector: cciptypes.ChainSelector(msg.Header.SourceChainSelector), + DestChainSelector: cciptypes.ChainSelector(msg.Header.DestChainSelector), + SequenceNumber: cciptypes.SeqNum(msg.Header.SequenceNumber), + Nonce: msg.Header.Nonce, + } + + // Convert token amounts + tokenAmounts := make([]cciptypes.RampTokenAmount, len(msg.TokenAmounts)) + for i, ta := range msg.TokenAmounts { + tokenAmounts[i] = cciptypes.RampTokenAmount{ + SourcePoolAddress: cciptypes.UnknownAddress(ta.SourcePoolAddress.Bytes()), + DestTokenAddress: cciptypes.UnknownAddress(ta.DestTokenAddress), + ExtraData: cciptypes.Bytes(ta.ExtraData), + Amount: cciptypes.BigInt{Int: ta.Amount}, + DestExecData: cciptypes.Bytes(ta.DestExecData), + } + } + + return cciptypes.Message{ + Header: header, + Sender: cciptypes.UnknownAddress(msg.Sender.Bytes()), + Data: cciptypes.Bytes(msg.Data), + Receiver: cciptypes.UnknownAddress(msg.Receiver), + ExtraArgs: cciptypes.Bytes(msg.ExtraArgs), + FeeToken: cciptypes.UnknownAddress(msg.FeeToken.Bytes()), + FeeTokenAmount: cciptypes.BigInt{Int: msg.FeeTokenAmount}, + FeeValueJuels: cciptypes.BigInt{Int: msg.FeeValueJuels}, + TokenAmounts: tokenAmounts, + } +} + +func updateGasPrice(env changeset.DeployedEnv, state changeset.CCIPOnChainState, srcChain, destChain uint64, gasPrice *big.Int) error { + chainState, exists := state.Chains[srcChain] + if !exists { + return fmt.Errorf("chain state not found for selector: %d", srcChain) + } + + feeQuoter := chainState.FeeQuoter + // Update gas price + auth := env.Env.Chains[srcChain].DeployerKey + tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: nil, + GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ + { + DestChainSelector: destChain, + UsdPerUnitGas: gasPrice, + }, + }, + }) + if err != nil { + return errors.Wrapf(err, "updating gas price on chain %d", srcChain) + } + if _, err := deployment.ConfirmIfNoError(env.Env.Chains[srcChain], tx, err); err != nil { + return err + } + + return nil +} + +func updateTokensPrices(env changeset.DeployedEnv, state changeset.CCIPOnChainState, chain uint64, tokenPrices map[common.Address]*big.Int) error { + chainState, exists := state.Chains[chain] + if !exists { + return fmt.Errorf("chain state not found for selector: %d", chain) + } + + feeQuoter := chainState.FeeQuoter + // Update token prices + auth := env.Env.Chains[chain].DeployerKey + tokenPricesUpdates := make([]fee_quoter.InternalTokenPriceUpdate, 0, len(tokenPrices)) + for token, price := range tokenPrices { + tokenPricesUpdates = append(tokenPricesUpdates, fee_quoter.InternalTokenPriceUpdate{ + SourceToken: token, + UsdPerToken: price, + }) + } + tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: tokenPricesUpdates, + GasPriceUpdates: nil, + }) + if err != nil { + return errors.Wrapf(err, "updating token prices on chain %d", chain) + } + if _, err := deployment.ConfirmIfNoError(env.Env.Chains[chain], tx, err); err != nil { + return err + } + + return nil +} diff --git a/integration-tests/smoke/ccip/ccip_fees_test.go b/integration-tests/smoke/ccip/ccip_fees_test.go new file mode 100644 index 00000000000..adf73edad7a --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_fees_test.go @@ -0,0 +1,449 @@ +package smoke + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/weth9_wrapper" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +// setupTokens deploys transferable tokens on the source and dest, mints tokens for the source and dest, and +// approves the router to spend the tokens +func setupTokens( + t *testing.T, + state changeset.CCIPOnChainState, + tenv changeset.DeployedEnv, + src, dest uint64, + transferTokenMintAmount, + feeTokenMintAmount *big.Int, +) ( + srcToken *burn_mint_erc677.BurnMintERC677, + dstToken *burn_mint_erc677.BurnMintERC677, +) { + lggr := logger.TestLogger(t) + e := tenv.Env + + // Deploy the token to test transferring + srcToken, _, dstToken, _, err := changeset.DeployTransferableToken( + lggr, + tenv.Env.Chains, + src, + dest, + tenv.Env.Chains[src].DeployerKey, + tenv.Env.Chains[dest].DeployerKey, + state, + tenv.Env.ExistingAddresses, + "MY_TOKEN", + ) + require.NoError(t, err) + + linkToken := state.Chains[src].LinkToken + + tx, err := srcToken.Mint( + e.Chains[src].DeployerKey, + e.Chains[src].DeployerKey.From, + transferTokenMintAmount, + ) + _, err = deployment.ConfirmIfNoError(e.Chains[src], tx, err) + require.NoError(t, err) + + // Mint a destination token + tx, err = dstToken.Mint( + e.Chains[dest].DeployerKey, + e.Chains[dest].DeployerKey.From, + transferTokenMintAmount, + ) + _, err = deployment.ConfirmIfNoError(e.Chains[dest], tx, err) + require.NoError(t, err) + + // Approve the router to spend the tokens and confirm the tx's + // To prevent having to approve the router for every transfer, we approve a sufficiently large amount + tx, err = srcToken.Approve(e.Chains[src].DeployerKey, state.Chains[src].Router.Address(), math.MaxBig256) + _, err = deployment.ConfirmIfNoError(e.Chains[src], tx, err) + require.NoError(t, err) + + tx, err = dstToken.Approve(e.Chains[dest].DeployerKey, state.Chains[dest].Router.Address(), math.MaxBig256) + _, err = deployment.ConfirmIfNoError(e.Chains[dest], tx, err) + require.NoError(t, err) + + // Grant mint and burn roles to the deployer key for the newly deployed linkToken + // Since those roles are not granted automatically + tx, err = linkToken.GrantMintAndBurnRoles(e.Chains[src].DeployerKey, e.Chains[src].DeployerKey.From) + _, err = deployment.ConfirmIfNoError(e.Chains[src], tx, err) + require.NoError(t, err) + + // Mint link token and confirm the tx + tx, err = linkToken.Mint( + e.Chains[src].DeployerKey, + e.Chains[src].DeployerKey.From, + feeTokenMintAmount, + ) + _, err = deployment.ConfirmIfNoError(e.Chains[src], tx, err) + require.NoError(t, err) + + return srcToken, dstToken +} + +func Test_CCIPFees(t *testing.T) { + t.Parallel() + tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, nil) + e := tenv.Env + + allChains := tenv.Env.AllChainSelectors() + require.Len(t, allChains, 2, "need two chains for this test") + sourceChain := allChains[0] + destChain := allChains[1] + + // Get new state after migration. + state, err := changeset.LoadOnchainState(e) + require.NoError(t, err) + + srcToken, dstToken := setupTokens( + t, + state, + tenv, + sourceChain, + destChain, + deployment.E18Mult(10_000), + deployment.E18Mult(10_000), + ) + + // Ensure capreg logs are up to date. + changeset.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) + + // Add all lanes + require.NoError(t, changeset.AddLanesForAll(e, state)) + + t.Run("Send programmable token transfer pay with Link token", func(t *testing.T) { + runFeeTokenTestCase(feeTokenTestCase{ + t: t, + dst: destChain, + src: sourceChain, + env: tenv, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken.Address(), + Amount: deployment.E18Mult(2), + }, + }, + feeToken: state.Chains[sourceChain].LinkToken.Address(), + data: []byte("hello ptt world"), + receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + srcToken: srcToken, + dstToken: dstToken, + assertTokenBalance: true, + }) + }) + + t.Run("Send programmable token transfer pay with native", func(t *testing.T) { + runFeeTokenTestCase(feeTokenTestCase{ + t: t, + + // note the order of src and dest is reversed here + src: destChain, + dst: sourceChain, + + env: tenv, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: dstToken.Address(), + Amount: deployment.E18Mult(2), + }, + }, + feeToken: common.HexToAddress("0x0"), + data: []byte("hello ptt world"), + receiver: common.LeftPadBytes(state.Chains[sourceChain].Receiver.Address().Bytes(), 32), + + // note the order of src and dest is reversed here + srcToken: dstToken, + dstToken: srcToken, + assertTokenBalance: true, + }) + }) + + t.Run("Send programmable token transfer pay with wrapped native", func(t *testing.T) { + runFeeTokenTestCase(feeTokenTestCase{ + t: t, + src: sourceChain, + dst: destChain, + env: tenv, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken.Address(), + Amount: deployment.E18Mult(2), + }, + }, + feeToken: state.Chains[sourceChain].Weth9.Address(), + data: []byte("hello ptt world"), + receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + srcToken: srcToken, + dstToken: dstToken, + assertTokenBalance: true, + }) + }) + + t.Run("Send programmable token transfer but revert not enough tokens", func(t *testing.T) { + // Send to the receiver on the destination chain paying with LINK token + var ( + receiver = common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32) + data = []byte("") + feeToken = state.Chains[sourceChain].LinkToken.Address() + ) + + // Increase the token send amount to more than available to intentionally cause a revert + ccipMessage := router.ClientEVM2AnyMessage{ + Receiver: receiver, + Data: data, + TokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken.Address(), + Amount: deployment.E18Mult(100_000_000), + }, + }, + FeeToken: feeToken, + ExtraArgs: nil, + } + + _, _, err = changeset.CCIPSendRequest( + e, + state, + sourceChain, destChain, + true, + ccipMessage, + ) + require.Error(t, err) + }) + + t.Run("Send data-only message pay with link token", func(t *testing.T) { + runFeeTokenTestCase(feeTokenTestCase{ + t: t, + src: sourceChain, + dst: destChain, + env: tenv, + // no tokens, only data + tokenAmounts: nil, + feeToken: state.Chains[sourceChain].LinkToken.Address(), + data: []byte("hello link world"), + receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + srcToken: srcToken, + dstToken: dstToken, + assertTokenBalance: false, + }) + }) + + t.Run("Send message pay with native", func(t *testing.T) { + runFeeTokenTestCase(feeTokenTestCase{ + t: t, + src: sourceChain, + dst: destChain, + env: tenv, + // no tokens, only data + tokenAmounts: nil, + feeToken: common.HexToAddress("0x0"), + data: []byte("hello native world"), + receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + srcToken: srcToken, + dstToken: dstToken, + assertTokenBalance: false, + }) + }) + + t.Run("Send message pay with wrapped native", func(t *testing.T) { + runFeeTokenTestCase(feeTokenTestCase{ + t: t, + src: sourceChain, + dst: destChain, + env: tenv, + // no tokens, only data + tokenAmounts: nil, + feeToken: state.Chains[sourceChain].Weth9.Address(), + data: []byte("hello wrapped native world"), + receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + srcToken: srcToken, + dstToken: dstToken, + assertTokenBalance: false, + }) + }) +} + +type feeTokenTestCase struct { + t *testing.T + src, dst uint64 + env changeset.DeployedEnv + srcToken, dstToken *burn_mint_erc677.BurnMintERC677 + tokenAmounts []router.ClientEVMTokenAmount + feeToken common.Address + receiver []byte + data []byte + assertTokenBalance bool +} + +func runFeeTokenTestCase(tc feeTokenTestCase) { + ctx := tests.Context(tc.t) + // Need to keep track of the block number for each chain so that event subscription can be done from that block. + startBlocks := make(map[uint64]*uint64) + expectedSeqNum := make(map[changeset.SourceDestPair]uint64) + expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) + + srcChain := tc.env.Env.Chains[tc.src] + dstChain := tc.env.Env.Chains[tc.dst] + + state, err := changeset.LoadOnchainState(tc.env.Env) + require.NoError(tc.t, err) + + var dstTokBalanceBefore *big.Int + if tc.assertTokenBalance { + var err error + dstTokBalanceBefore, err = tc.dstToken.BalanceOf(nil, state.Chains[tc.dst].Receiver.Address()) + require.NoError(tc.t, err) + tc.t.Logf("destination token balance before of receiver %s: %s", + state.Chains[tc.dst].Receiver.Address(), + dstTokBalanceBefore.String()) + } + + // if fee token is not native then approve the router to spend the fee token from the sender. + var feeTokenWrapper *burn_mint_erc677.BurnMintERC677 + if tc.feeToken != common.HexToAddress("0x0") { + if tc.feeToken == state.Chains[tc.src].Weth9.Address() { + // Deposit some ETH into the WETH contract + weth9, err := weth9_wrapper.NewWETH9(state.Chains[tc.src].Weth9.Address(), srcChain.Client) + require.NoError(tc.t, err) + + balance, err := srcChain.Client.BalanceAt(ctx, srcChain.DeployerKey.From, nil) + require.NoError(tc.t, err) + + tc.t.Logf("balance before deposit: %s", balance.String()) + + srcChain.DeployerKey.Value = assets.Ether(100).ToInt() + tx, err := weth9.Deposit(srcChain.DeployerKey) + _, err = deployment.ConfirmIfNoError(srcChain, tx, err) + require.NoError(tc.t, err) + srcChain.DeployerKey.Value = big.NewInt(0) + } + + var err error + feeTokenWrapper, err = burn_mint_erc677.NewBurnMintERC677(tc.feeToken, srcChain.Client) + require.NoError(tc.t, err) + + // Approve the router to spend fee token + tx, err := feeTokenWrapper.Approve(srcChain.DeployerKey, state.Chains[tc.src].Router.Address(), math.MaxBig256) + + _, err = deployment.ConfirmIfNoError(srcChain, tx, err) + require.NoError(tc.t, err) + } + + // get the header for the destination chain and the relevant block number + latesthdr, err := dstChain.Client.HeaderByNumber(testcontext.Get(tc.t), nil) + require.NoError(tc.t, err) + block := latesthdr.Number.Uint64() + startBlocks[tc.dst] = &block + + // Get the fee Token Balance Before, if not fee token set get native balance. + var feeTokenBalanceBefore *big.Int + if feeTokenWrapper != nil { + feeTokenBalanceBefore, err = feeTokenWrapper.BalanceOf(&bind.CallOpts{ + Context: ctx, + }, srcChain.DeployerKey.From) + require.NoError(tc.t, err) + } else { + feeTokenBalanceBefore, err = srcChain.Client.BalanceAt(ctx, srcChain.DeployerKey.From, nil) + require.NoError(tc.t, err) + } + tc.t.Logf("fee token balance before: %s, fee token enabled: %s", + feeTokenBalanceBefore.String(), tc.feeToken.String()) + + msgSentEvent := changeset.TestSendRequest( + tc.t, + tc.env.Env, + state, + tc.src, + tc.dst, + false, + router.ClientEVM2AnyMessage{ + Receiver: tc.receiver, + Data: tc.data, + TokenAmounts: tc.tokenAmounts, + FeeToken: tc.feeToken, + ExtraArgs: nil, + }, + ) + + expectedSeqNum[changeset.SourceDestPair{ + SourceChainSelector: tc.src, + DestChainSelector: tc.dst, + }] = msgSentEvent.SequenceNumber + expectedSeqNumExec[changeset.SourceDestPair{ + SourceChainSelector: tc.src, + DestChainSelector: tc.dst, + }] = []uint64{msgSentEvent.SequenceNumber} + + // Check the fee token balance after the request and ensure fee tokens were spent + var feeTokenBalanceAfter *big.Int + if feeTokenWrapper != nil { + feeTokenBalanceAfter, err = feeTokenWrapper.BalanceOf(&bind.CallOpts{ + Context: ctx, + }, srcChain.DeployerKey.From) + require.NoError(tc.t, err) + } else { + feeTokenBalanceAfter, err = srcChain.Client.BalanceAt(ctx, srcChain.DeployerKey.From, nil) + require.NoError(tc.t, err) + } + tc.t.Logf("fee token balance after: %s, fee token: %s, fee paid: %s", + feeTokenBalanceAfter.String(), tc.feeToken.String(), msgSentEvent.Message.FeeTokenAmount) + // in the case we have no fee token, native is also used to pay for the tx, + // so we have to subtract that as well + if feeTokenWrapper == nil { + receipt, err := srcChain.Client.TransactionReceipt(ctx, msgSentEvent.Raw.TxHash) + require.NoError(tc.t, err) + txCostWei := new(big.Int).Mul(new(big.Int).SetUint64(receipt.GasUsed), receipt.EffectiveGasPrice) + feeTokenBalanceBefore.Sub(feeTokenBalanceBefore, txCostWei) + } + require.Equal( + tc.t, + feeTokenBalanceAfter, + new(big.Int).Sub(feeTokenBalanceBefore, msgSentEvent.Message.FeeTokenAmount), + ) + + // Wait for all commit reports to land. + changeset.ConfirmCommitForAllWithExpectedSeqNums(tc.t, tc.env.Env, state, expectedSeqNum, startBlocks) + + // After commit is reported on all chains, token prices should be updated in FeeQuoter. + linkAddress := state.Chains[tc.dst].LinkToken.Address() + feeQuoter := state.Chains[tc.dst].FeeQuoter + timestampedPrice, err := feeQuoter.GetTokenPrice(&bind.CallOpts{ + Context: ctx, + }, linkAddress) + require.NoError(tc.t, err) + require.Equal(tc.t, changeset.MockLinkPrice, timestampedPrice.Value) + + // Wait for all exec reports to land + changeset.ConfirmExecWithSeqNrsForAll(tc.t, tc.env.Env, state, expectedSeqNumExec, startBlocks) + + if tc.assertTokenBalance { + require.Len(tc.t, tc.tokenAmounts, 1) + expectedTransferAmount := tc.tokenAmounts[0].Amount + + balanceAfter, err := tc.dstToken.BalanceOf(&bind.CallOpts{ + Context: ctx, + }, state.Chains[tc.dst].Receiver.Address()) + require.NoError(tc.t, err) + require.Equal( + tc.t, + new(big.Int).Add(dstTokBalanceBefore, expectedTransferAmount), + balanceAfter, + ) + } +} diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index 4083be1c6be..201982e19d1 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -1,9 +1,14 @@ package smoke import ( + "context" + "encoding/binary" + "errors" "math/big" "os" + "slices" "strconv" + "strings" "testing" "time" @@ -13,8 +18,11 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot/rmn/types" + "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/deployment/environment/devenv" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" @@ -75,7 +83,7 @@ func TestRMN_MultipleMessagesOnOneLaneNoWaitForExec(t *testing.T) { func TestRMN_NotEnoughObservers(t *testing.T) { runRmnTestCase(t, rmnTestCase{ name: "one message but not enough observers, should not get a commit report", - passIfNoCommitAfter: time.Minute, // wait for a minute and assert that commit report was not delivered + passIfNoCommitAfter: 15 * time.Second, homeChainConfig: homeChainConfig{ f: map[int]int{chain0: 1, chain1: 1}, }, @@ -121,7 +129,7 @@ func TestRMN_DifferentSigners(t *testing.T) { func TestRMN_NotEnoughSigners(t *testing.T) { runRmnTestCase(t, rmnTestCase{ name: "different signers and different observers", - passIfNoCommitAfter: time.Minute, // wait for a minute and assert that commit report was not delivered + passIfNoCommitAfter: 15 * time.Second, homeChainConfig: homeChainConfig{ f: map[int]int{chain0: 1, chain1: 1}, }, @@ -169,70 +177,77 @@ func TestRMN_DifferentRmnNodesForDifferentChains(t *testing.T) { }) } +func TestRMN_TwoMessagesOneSourceChainCursed(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "two messages, one source chain is cursed", + passIfNoCommitAfter: 15 * time.Second, + cursedSubjectsPerChain: map[int][]int{ + chain1: {chain0}, + }, + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 2, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, // <----- this message should not be committed + {fromChainIdx: chain1, toChainIdx: chain0, count: 1}, + }, + }) +} + +func TestRMN_GlobalCurseTwoMessagesOnTwoLanes(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "global curse messages on two lanes", + waitForExec: false, + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 2, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, + {fromChainIdx: chain1, toChainIdx: chain0, count: 5}, + }, + cursedSubjectsPerChain: map[int][]int{ + chain1: {globalCurse}, + chain0: {globalCurse}, + }, + passIfNoCommitAfter: 15 * time.Second, + }) +} + const ( - chain0 = 0 - chain1 = 1 + chain0 = 0 + chain1 = 1 + globalCurse = 1000 ) func runRmnTestCase(t *testing.T, tc rmnTestCase) { require.NoError(t, os.Setenv("ENABLE_RMN", "true")) + require.NoError(t, tc.validate()) + ctx := testcontext.Get(t) t.Logf("Running RMN test case: %s", tc.name) envWithRMN, rmnCluster := testsetups.NewLocalDevEnvironmentWithRMN(t, logger.TestLogger(t), len(tc.rmnNodes)) t.Logf("envWithRmn: %#v", envWithRMN) - var chainSelectors []uint64 - for _, chain := range envWithRMN.Env.Chains { - chainSelectors = append(chainSelectors, chain.Selector) - } - require.Greater(t, len(chainSelectors), 1, "There should be at least two chains") - - remoteChainSelectors := make([]uint64, 0, len(envWithRMN.Env.Chains)-1) - for _, chain := range envWithRMN.Env.Chains { - remoteChainSelectors = append(remoteChainSelectors, chain.Selector) - } - require.Greater(t, len(remoteChainSelectors), 0, "There should be at least one remote chain") - - var ( - rmnHomeNodes []rmn_home.RMNHomeNode - rmnRemoteSigners []rmn_remote.RMNRemoteSigner - ) - - for _, rmnNodeInfo := range tc.rmnNodes { - rmn := rmnCluster.Nodes["rmn_"+strconv.Itoa(rmnNodeInfo.id)] - - var offchainPublicKey [32]byte - copy(offchainPublicKey[:], rmn.RMN.OffchainPublicKey) - - rmnHomeNodes = append(rmnHomeNodes, rmn_home.RMNHomeNode{ - PeerId: rmn.Proxy.PeerID, - OffchainPublicKey: offchainPublicKey, - }) - - if rmnNodeInfo.isSigner { - if rmnNodeInfo.id < 0 { - t.Fatalf("node id is negative: %d", rmnNodeInfo.id) - } - rmnRemoteSigners = append(rmnRemoteSigners, rmn_remote.RMNRemoteSigner{ - OnchainPublicKey: rmn.RMN.EVMOnchainPublicKey, - NodeIndex: uint64(rmnNodeInfo.id), - }) - } - } - - var rmnHomeSourceChains []rmn_home.RMNHomeSourceChain - for remoteChainIdx, remoteF := range tc.homeChainConfig.f { - if remoteF < 0 { - t.Fatalf("negative remote F: %d", remoteF) - } - // configure remote chain details on the home contract - rmnHomeSourceChains = append(rmnHomeSourceChains, rmn_home.RMNHomeSourceChain{ - ChainSelector: chainSelectors[remoteChainIdx], - F: uint64(remoteF), - ObserverNodesBitmap: createObserverNodesBitmap(chainSelectors[remoteChainIdx], tc.rmnNodes, chainSelectors), - }) - } + tc.populateFields(t, envWithRMN, rmnCluster) onChainState, err := changeset.LoadOnchainState(envWithRMN.Env) require.NoError(t, err) @@ -244,22 +259,14 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { homeChainState, ok := onChainState.Chains[envWithRMN.HomeChainSel] require.True(t, ok) - allDigests, err := homeChainState.RMNHome.GetConfigDigests(&bind.CallOpts{ - Context: testcontext.Get(t), - }) + allDigests, err := homeChainState.RMNHome.GetConfigDigests(&bind.CallOpts{Context: ctx}) require.NoError(t, err) t.Logf("RMNHome candidateDigest before setting new candidate: %x, activeDigest: %x", allDigests.CandidateConfigDigest[:], allDigests.ActiveConfigDigest[:]) - staticConfig := rmn_home.RMNHomeStaticConfig{ - Nodes: rmnHomeNodes, - OffchainConfig: []byte{}, - } - dynamicConfig := rmn_home.RMNHomeDynamicConfig{ - SourceChains: rmnHomeSourceChains, - OffchainConfig: []byte{}, - } + staticConfig := rmn_home.RMNHomeStaticConfig{Nodes: tc.pf.rmnHomeNodes, OffchainConfig: []byte{}} + dynamicConfig := rmn_home.RMNHomeDynamicConfig{SourceChains: tc.pf.rmnHomeSourceChains, OffchainConfig: []byte{}} t.Logf("Setting RMNHome candidate with staticConfig: %+v, dynamicConfig: %+v, current candidateDigest: %x", staticConfig, dynamicConfig, allDigests.CandidateConfigDigest[:]) tx, err := homeChainState.RMNHome.SetCandidate(homeChain.DeployerKey, staticConfig, dynamicConfig, allDigests.CandidateConfigDigest) @@ -268,9 +275,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { _, err = deployment.ConfirmIfNoError(homeChain, tx, err) require.NoError(t, err) - candidateDigest, err := homeChainState.RMNHome.GetCandidateDigest(&bind.CallOpts{ - Context: testcontext.Get(t), - }) + candidateDigest, err := homeChainState.RMNHome.GetCandidateDigest(&bind.CallOpts{Context: ctx}) require.NoError(t, err) t.Logf("RMNHome candidateDigest after setting new candidate: %x", candidateDigest[:]) @@ -284,105 +289,70 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { require.NoError(t, err) // check the active digest is the same as the candidate digest - activeDigest, err := homeChainState.RMNHome.GetActiveDigest(&bind.CallOpts{ - Context: testcontext.Get(t), - }) + activeDigest, err := homeChainState.RMNHome.GetActiveDigest(&bind.CallOpts{Context: ctx}) require.NoError(t, err) require.Equalf(t, candidateDigest, activeDigest, "active digest should be the same as the previously candidate digest after promotion, previous candidate: %x, active: %x", candidateDigest[:], activeDigest[:]) - // Set RMN remote config appropriately - for _, remoteCfg := range tc.remoteChainsConfig { - remoteSel := chainSelectors[remoteCfg.chainIdx] - chState, ok := onChainState.Chains[remoteSel] - require.True(t, ok) - if remoteCfg.f < 0 { - t.Fatalf("negative F: %d", remoteCfg.f) - } - rmnRemoteConfig := rmn_remote.RMNRemoteConfig{ - RmnHomeContractConfigDigest: activeDigest, - Signers: rmnRemoteSigners, - F: uint64(remoteCfg.f), - } + tc.setRmnRemoteConfig(ctx, t, onChainState, activeDigest, envWithRMN) - chain := envWithRMN.Env.Chains[chainSelectors[remoteCfg.chainIdx]] - - t.Logf("Setting RMNRemote config with RMNHome active digest: %x, cfg: %+v", activeDigest[:], rmnRemoteConfig) - tx2, err2 := chState.RMNRemote.SetConfig(chain.DeployerKey, rmnRemoteConfig) - require.NoError(t, err2) - _, err2 = deployment.ConfirmIfNoError(chain, tx2, err2) - require.NoError(t, err2) + tc.killMarkedRmnNodes(t, rmnCluster) - // confirm the config is set correctly - config, err2 := chState.RMNRemote.GetVersionedConfig(&bind.CallOpts{ - Context: testcontext.Get(t), - }) - require.NoError(t, err2) - require.Equalf(t, - activeDigest, - config.Config.RmnHomeContractConfigDigest, - "RMNRemote config digest should be the same as the active digest of RMNHome after setting, RMNHome active: %x, RMNRemote config: %x", - activeDigest[:], config.Config.RmnHomeContractConfigDigest[:]) + changeset.ReplayLogs(t, envWithRMN.Env.Offchain, envWithRMN.ReplayBlocks) + require.NoError(t, changeset.AddLanesForAll(envWithRMN.Env, onChainState)) + disabledNodes := tc.disableOraclesIfThisIsACursingTestCase(ctx, t, envWithRMN) - t.Logf("RMNRemote config digest after setting: %x", config.Config.RmnHomeContractConfigDigest[:]) - } + startBlocks, seqNumCommit, seqNumExec := tc.sendMessages(t, onChainState, envWithRMN) + t.Logf("Sent all messages, seqNumCommit: %v seqNumExec: %v", seqNumCommit, seqNumExec) - // Kill the RMN nodes that are marked for force exit - for _, n := range tc.rmnNodes { - if n.forceExit { - t.Logf("Pausing RMN node %d", n.id) - rmnN := rmnCluster.Nodes["rmn_"+strconv.Itoa(n.id)] - require.NoError(t, osutil.ExecCmd(zerolog.Nop(), "docker kill "+rmnN.Proxy.ContainerName)) - t.Logf("Paused RMN node %d", n.id) - } - } + tc.callContractsToCurseChains(ctx, t, onChainState, envWithRMN) - changeset.ReplayLogs(t, envWithRMN.Env.Offchain, envWithRMN.ReplayBlocks) - // Add all lanes - require.NoError(t, changeset.AddLanesForAll(envWithRMN.Env, onChainState)) + tc.enableOracles(ctx, t, envWithRMN, disabledNodes) - // Need to keep track of the block number for each chain so that event subscription can be done from that block. - startBlocks := make(map[uint64]*uint64) expectedSeqNum := make(map[changeset.SourceDestPair]uint64) - expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) - for _, msg := range tc.messagesToSend { - fromChain := chainSelectors[msg.fromChainIdx] - toChain := chainSelectors[msg.toChainIdx] + for k, v := range seqNumCommit { + cursedSubjectsOfDest, exists := tc.pf.cursedSubjectsPerChainSel[k.DestChainSelector] + shouldSkip := exists && (slices.Contains(cursedSubjectsOfDest, globalCurse) || + slices.Contains(cursedSubjectsOfDest, k.SourceChainSelector)) - for i := 0; i < msg.count; i++ { - msgSentEvent := changeset.TestSendRequest(t, envWithRMN.Env, onChainState, fromChain, toChain, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(onChainState.Chains[toChain].Receiver.Address().Bytes(), 32), - Data: []byte("hello world"), - TokenAmounts: nil, - FeeToken: common.HexToAddress("0x0"), - ExtraArgs: nil, - }) - expectedSeqNum[changeset.SourceDestPair{ - SourceChainSelector: fromChain, - DestChainSelector: toChain, - }] = msgSentEvent.SequenceNumber - expectedSeqNumExec[changeset.SourceDestPair{ - SourceChainSelector: fromChain, - DestChainSelector: toChain, - }] = []uint64{msgSentEvent.SequenceNumber} - t.Logf("Sent message from chain %d to chain %d with seqNum %d", fromChain, toChain, msgSentEvent.SequenceNumber) + if !shouldSkip { + expectedSeqNum[k] = v } + } - zero := uint64(0) - startBlocks[toChain] = &zero + t.Logf("expectedSeqNums: %v", expectedSeqNum) + t.Logf("expectedSeqNums including cursed chains: %v", seqNumCommit) + + if len(tc.cursedSubjectsPerChain) > 0 && len(seqNumCommit) == len(expectedSeqNum) { + t.Fatalf("test case is wrong: no message was sent to non-cursed chains when you " + + "define curse subjects, your test case should have at least one message not expected to be delivered") } - t.Logf("Sent all messages, expectedSeqNum: %v", expectedSeqNum) commitReportReceived := make(chan struct{}) go func() { - changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) - commitReportReceived <- struct{}{} + if len(expectedSeqNum) > 0 { + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) + commitReportReceived <- struct{}{} + } + + if len(seqNumCommit) > 0 && len(seqNumCommit) > len(expectedSeqNum) { + // wait for a duration and assert that commit reports were not delivered for cursed source chains + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, seqNumCommit, startBlocks) + commitReportReceived <- struct{}{} + } }() if tc.passIfNoCommitAfter > 0 { // wait for a duration and assert that commit reports were not delivered + if len(expectedSeqNum) > 0 && len(seqNumCommit) > len(expectedSeqNum) { + t.Logf("⌛ Waiting for commit reports of non-cursed chains...") + <-commitReportReceived + t.Logf("✅ Commit reports of non-cursed chains received") + } + tim := time.NewTimer(tc.passIfNoCommitAfter) t.Logf("waiting for %s before asserting that commit report was not received", tc.passIfNoCommitAfter) + select { case <-commitReportReceived: t.Errorf("Commit report was received while it was not expected") @@ -398,7 +368,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { if tc.waitForExec { t.Logf("⌛ Waiting for exec reports...") - changeset.ConfirmExecWithSeqNrsForAll(t, envWithRMN.Env, onChainState, expectedSeqNumExec, startBlocks) + changeset.ConfirmExecWithSeqNrsForAll(t, envWithRMN.Env, onChainState, seqNumExec, startBlocks) t.Logf("✅ Exec report") } } @@ -447,10 +417,236 @@ type rmnTestCase struct { name string // If set to 0, the test will wait for commit reports. // If set to a positive value, the test will wait for that duration and will assert that commit report was not delivered. - passIfNoCommitAfter time.Duration - waitForExec bool - homeChainConfig homeChainConfig - remoteChainsConfig []remoteChainConfig - rmnNodes []rmnNode - messagesToSend []messageToSend + passIfNoCommitAfter time.Duration + cursedSubjectsPerChain map[int][]int + waitForExec bool + homeChainConfig homeChainConfig + remoteChainsConfig []remoteChainConfig + rmnNodes []rmnNode + messagesToSend []messageToSend + + // populated fields after environment setup + pf testCasePopulatedFields +} + +type testCasePopulatedFields struct { + chainSelectors []uint64 + rmnHomeNodes []rmn_home.RMNHomeNode + rmnRemoteSigners []rmn_remote.RMNRemoteSigner + rmnHomeSourceChains []rmn_home.RMNHomeSourceChain + cursedSubjectsPerChainSel map[uint64][]uint64 +} + +func (tc *rmnTestCase) populateFields(t *testing.T, envWithRMN changeset.DeployedEnv, rmnCluster devenv.RMNCluster) { + require.GreaterOrEqual(t, len(envWithRMN.Env.Chains), 2, "test assumes at least two chains") + for _, chain := range envWithRMN.Env.Chains { + tc.pf.chainSelectors = append(tc.pf.chainSelectors, chain.Selector) + } + + for _, rmnNodeInfo := range tc.rmnNodes { + rmn := rmnCluster.Nodes["rmn_"+strconv.Itoa(rmnNodeInfo.id)] + + var offchainPublicKey [32]byte + copy(offchainPublicKey[:], rmn.RMN.OffchainPublicKey) + + tc.pf.rmnHomeNodes = append(tc.pf.rmnHomeNodes, rmn_home.RMNHomeNode{ + PeerId: rmn.Proxy.PeerID, + OffchainPublicKey: offchainPublicKey, + }) + + if rmnNodeInfo.isSigner { + if rmnNodeInfo.id < 0 { + t.Fatalf("node id is negative: %d", rmnNodeInfo.id) + } + tc.pf.rmnRemoteSigners = append(tc.pf.rmnRemoteSigners, rmn_remote.RMNRemoteSigner{ + OnchainPublicKey: rmn.RMN.EVMOnchainPublicKey, + NodeIndex: uint64(rmnNodeInfo.id), + }) + } + } + + for remoteChainIdx, remoteF := range tc.homeChainConfig.f { + if remoteF < 0 { + t.Fatalf("negative remote F: %d", remoteF) + } + // configure remote chain details on the home contract + tc.pf.rmnHomeSourceChains = append(tc.pf.rmnHomeSourceChains, rmn_home.RMNHomeSourceChain{ + ChainSelector: tc.pf.chainSelectors[remoteChainIdx], + F: uint64(remoteF), + ObserverNodesBitmap: createObserverNodesBitmap(tc.pf.chainSelectors[remoteChainIdx], tc.rmnNodes, tc.pf.chainSelectors), + }) + } + + // populate cursed subjects with actual chain selectors + tc.pf.cursedSubjectsPerChainSel = make(map[uint64][]uint64) + for chainIdx, subjects := range tc.cursedSubjectsPerChain { + chainSel := tc.pf.chainSelectors[chainIdx] + for _, subject := range subjects { + subjSel := uint64(globalCurse) + if subject != globalCurse { + subjSel = tc.pf.chainSelectors[subject] + } + tc.pf.cursedSubjectsPerChainSel[chainSel] = append(tc.pf.cursedSubjectsPerChainSel[chainSel], subjSel) + } + } +} + +func (tc rmnTestCase) validate() error { + if len(tc.cursedSubjectsPerChain) > 0 && tc.passIfNoCommitAfter == 0 { + return errors.New("when you define cursed subjects you also need to define the duration that the " + + "test will wait for non-transmitted roots") + } + return nil +} + +func (tc rmnTestCase) setRmnRemoteConfig( + ctx context.Context, + t *testing.T, + onChainState changeset.CCIPOnChainState, + activeDigest [32]byte, + envWithRMN changeset.DeployedEnv) { + for _, remoteCfg := range tc.remoteChainsConfig { + remoteSel := tc.pf.chainSelectors[remoteCfg.chainIdx] + chState, ok := onChainState.Chains[remoteSel] + require.True(t, ok) + if remoteCfg.f < 0 { + t.Fatalf("negative F: %d", remoteCfg.f) + } + rmnRemoteConfig := rmn_remote.RMNRemoteConfig{ + RmnHomeContractConfigDigest: activeDigest, + Signers: tc.pf.rmnRemoteSigners, + F: uint64(remoteCfg.f), + } + + chain := envWithRMN.Env.Chains[tc.pf.chainSelectors[remoteCfg.chainIdx]] + + t.Logf("Setting RMNRemote config with RMNHome active digest: %x, cfg: %+v", activeDigest[:], rmnRemoteConfig) + tx2, err2 := chState.RMNRemote.SetConfig(chain.DeployerKey, rmnRemoteConfig) + require.NoError(t, err2) + _, err2 = deployment.ConfirmIfNoError(chain, tx2, err2) + require.NoError(t, err2) + + // confirm the config is set correctly + config, err2 := chState.RMNRemote.GetVersionedConfig(&bind.CallOpts{Context: ctx}) + require.NoError(t, err2) + require.Equalf(t, + activeDigest, + config.Config.RmnHomeContractConfigDigest, + "RMNRemote config digest should be the same as the active digest of RMNHome after setting, RMNHome active: %x, RMNRemote config: %x", + activeDigest[:], config.Config.RmnHomeContractConfigDigest[:]) + + t.Logf("RMNRemote config digest after setting: %x", config.Config.RmnHomeContractConfigDigest[:]) + } +} + +func (tc rmnTestCase) killMarkedRmnNodes(t *testing.T, rmnCluster devenv.RMNCluster) { + for _, n := range tc.rmnNodes { + if n.forceExit { + t.Logf("Pausing RMN node %d", n.id) + rmnN := rmnCluster.Nodes["rmn_"+strconv.Itoa(n.id)] + require.NoError(t, osutil.ExecCmd(zerolog.Nop(), "docker kill "+rmnN.Proxy.ContainerName)) + t.Logf("Paused RMN node %d", n.id) + } + } +} + +func (tc rmnTestCase) disableOraclesIfThisIsACursingTestCase(ctx context.Context, t *testing.T, envWithRMN changeset.DeployedEnv) []string { + disabledNodes := make([]string, 0) + + if len(tc.cursedSubjectsPerChain) > 0 { + listNodesResp, err := envWithRMN.Env.Offchain.ListNodes(ctx, &node.ListNodesRequest{}) + require.NoError(t, err) + + for _, n := range listNodesResp.Nodes { + if strings.HasPrefix(n.Name, "bootstrap") { + continue + } + _, err := envWithRMN.Env.Offchain.DisableNode(ctx, &node.DisableNodeRequest{Id: n.Id}) + require.NoError(t, err) + disabledNodes = append(disabledNodes, n.Id) + t.Logf("node %s disabled", n.Id) + } + } + + return disabledNodes +} + +func (tc rmnTestCase) sendMessages(t *testing.T, onChainState changeset.CCIPOnChainState, envWithRMN changeset.DeployedEnv) (map[uint64]*uint64, map[changeset.SourceDestPair]uint64, map[changeset.SourceDestPair][]uint64) { + startBlocks := make(map[uint64]*uint64) + seqNumCommit := make(map[changeset.SourceDestPair]uint64) + seqNumExec := make(map[changeset.SourceDestPair][]uint64) + + for _, msg := range tc.messagesToSend { + fromChain := tc.pf.chainSelectors[msg.fromChainIdx] + toChain := tc.pf.chainSelectors[msg.toChainIdx] + + for i := 0; i < msg.count; i++ { + msgSentEvent := changeset.TestSendRequest(t, envWithRMN.Env, onChainState, fromChain, toChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[toChain].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + seqNumCommit[changeset.SourceDestPair{ + SourceChainSelector: fromChain, + DestChainSelector: toChain, + }] = msgSentEvent.SequenceNumber + seqNumExec[changeset.SourceDestPair{ + SourceChainSelector: fromChain, + DestChainSelector: toChain, + }] = []uint64{msgSentEvent.SequenceNumber} + t.Logf("Sent message from chain %d to chain %d with seqNum %d", fromChain, toChain, msgSentEvent.SequenceNumber) + } + + zero := uint64(0) + startBlocks[toChain] = &zero + } + + return startBlocks, seqNumCommit, seqNumExec +} + +func (tc rmnTestCase) callContractsToCurseChains(ctx context.Context, t *testing.T, onChainState changeset.CCIPOnChainState, envWithRMN changeset.DeployedEnv) { + for _, remoteCfg := range tc.remoteChainsConfig { + remoteSel := tc.pf.chainSelectors[remoteCfg.chainIdx] + chState, ok := onChainState.Chains[remoteSel] + require.True(t, ok) + chain, ok := envWithRMN.Env.Chains[remoteSel] + require.True(t, ok) + + cursedSubjects, ok := tc.cursedSubjectsPerChain[remoteCfg.chainIdx] + if !ok { + continue // nothing to curse on this chain + } + + for _, subjectDescription := range cursedSubjects { + subj := types.GlobalCurseSubject + if subjectDescription != globalCurse { + subj = chainSelectorToBytes16(tc.pf.chainSelectors[subjectDescription]) + } + t.Logf("cursing subject %d (%d)", subj, subjectDescription) + txCurse, errCurse := chState.RMNRemote.Curse(chain.DeployerKey, subj) + _, errConfirm := deployment.ConfirmIfNoError(chain, txCurse, errCurse) + require.NoError(t, errConfirm) + } + + cs, err := chState.RMNRemote.GetCursedSubjects(&bind.CallOpts{Context: ctx}) + require.NoError(t, err) + t.Logf("Cursed subjects: %v", cs) + } +} + +func (tc rmnTestCase) enableOracles(ctx context.Context, t *testing.T, envWithRMN changeset.DeployedEnv, nodeIDs []string) { + for _, n := range nodeIDs { + _, err := envWithRMN.Env.Offchain.EnableNode(ctx, &node.EnableNodeRequest{Id: n}) + require.NoError(t, err) + t.Logf("node %s enabled", n) + } +} + +func chainSelectorToBytes16(chainSel uint64) [16]byte { + var result [16]byte + // Convert the uint64 to bytes and place it in the last 8 bytes of the array + binary.BigEndian.PutUint64(result[8:], chainSel) + return result } diff --git a/integration-tests/smoke/ccip/ccip_test.go b/integration-tests/smoke/ccip/ccip_test.go index d2adbaaa484..fb6fb2cf960 100644 --- a/integration-tests/smoke/ccip/ccip_test.go +++ b/integration-tests/smoke/ccip/ccip_test.go @@ -1,17 +1,14 @@ package smoke import ( - "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -19,7 +16,8 @@ import ( func TestInitialDeployOnLocal(t *testing.T) { t.Parallel() lggr := logger.TestLogger(t) - tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, nil) + config := &changeset.TestConfigs{} + tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, config) e := tenv.Env state, err := changeset.LoadOnchainState(e) require.NoError(t, err) @@ -75,134 +73,3 @@ func TestInitialDeployOnLocal(t *testing.T) { // TODO: Apply the proposal. } - -func TestTokenTransfer(t *testing.T) { - t.Parallel() - lggr := logger.TestLogger(t) - tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, nil) - e := tenv.Env - state, err := changeset.LoadOnchainState(e) - require.NoError(t, err) - - srcToken, _, dstToken, _, err := changeset.DeployTransferableToken( - lggr, - tenv.Env.Chains, - tenv.HomeChainSel, - tenv.FeedChainSel, - state, - e.ExistingAddresses, - "MY_TOKEN", - ) - require.NoError(t, err) - - // Add all lanes - require.NoError(t, changeset.AddLanesForAll(e, state)) - // Need to keep track of the block number for each chain so that event subscription can be done from that block. - startBlocks := make(map[uint64]*uint64) - // Send a message from each chain to every other chain. - expectedSeqNum := make(map[changeset.SourceDestPair]uint64) - expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) - - twoCoins := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2)) - tx, err := srcToken.Mint( - e.Chains[tenv.HomeChainSel].DeployerKey, - e.Chains[tenv.HomeChainSel].DeployerKey.From, - new(big.Int).Mul(twoCoins, big.NewInt(10)), - ) - require.NoError(t, err) - _, err = e.Chains[tenv.HomeChainSel].Confirm(tx) - require.NoError(t, err) - - tx, err = dstToken.Mint( - e.Chains[tenv.FeedChainSel].DeployerKey, - e.Chains[tenv.FeedChainSel].DeployerKey.From, - new(big.Int).Mul(twoCoins, big.NewInt(10)), - ) - require.NoError(t, err) - _, err = e.Chains[tenv.FeedChainSel].Confirm(tx) - require.NoError(t, err) - - tx, err = srcToken.Approve(e.Chains[tenv.HomeChainSel].DeployerKey, state.Chains[tenv.HomeChainSel].Router.Address(), twoCoins) - require.NoError(t, err) - _, err = e.Chains[tenv.HomeChainSel].Confirm(tx) - require.NoError(t, err) - tx, err = dstToken.Approve(e.Chains[tenv.FeedChainSel].DeployerKey, state.Chains[tenv.FeedChainSel].Router.Address(), twoCoins) - require.NoError(t, err) - _, err = e.Chains[tenv.FeedChainSel].Confirm(tx) - require.NoError(t, err) - - tokens := map[uint64][]router.ClientEVMTokenAmount{ - tenv.HomeChainSel: {{ - Token: srcToken.Address(), - Amount: twoCoins, - }}, - tenv.FeedChainSel: {{ - Token: dstToken.Address(), - Amount: twoCoins, - }}, - } - - for src := range e.Chains { - for dest, destChain := range e.Chains { - if src == dest { - continue - } - latesthdr, err := destChain.Client.HeaderByNumber(testcontext.Get(t), nil) - require.NoError(t, err) - block := latesthdr.Number.Uint64() - startBlocks[dest] = &block - - var ( - receiver = common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32) - data = []byte("hello world") - feeToken = common.HexToAddress("0x0") - msgSentEvent *onramp.OnRampCCIPMessageSent - ) - if src == tenv.HomeChainSel && dest == tenv.FeedChainSel { - msgSentEvent = changeset.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ - Receiver: receiver, - Data: data, - TokenAmounts: tokens[src], - FeeToken: feeToken, - ExtraArgs: nil, - }) - } else { - msgSentEvent = changeset.TestSendRequest(t, e, state, src, dest, false, router.ClientEVM2AnyMessage{ - Receiver: receiver, - Data: data, - TokenAmounts: nil, - FeeToken: feeToken, - ExtraArgs: nil, - }) - } - - expectedSeqNum[changeset.SourceDestPair{ - SourceChainSelector: src, - DestChainSelector: dest, - }] = msgSentEvent.SequenceNumber - expectedSeqNumExec[changeset.SourceDestPair{ - SourceChainSelector: src, - DestChainSelector: dest, - }] = []uint64{msgSentEvent.SequenceNumber} - } - } - - // Wait for all commit reports to land. - changeset.ConfirmCommitForAllWithExpectedSeqNums(t, e, state, expectedSeqNum, startBlocks) - - // After commit is reported on all chains, token prices should be updated in FeeQuoter. - for dest := range e.Chains { - linkAddress := state.Chains[dest].LinkToken.Address() - feeQuoter := state.Chains[dest].FeeQuoter - timestampedPrice, err := feeQuoter.GetTokenPrice(nil, linkAddress) - require.NoError(t, err) - require.Equal(t, changeset.MockLinkPrice, timestampedPrice.Value) - } - - // Wait for all exec reports to land - changeset.ConfirmExecWithSeqNrsForAll(t, e, state, expectedSeqNumExec, startBlocks) - - balance, err := dstToken.BalanceOf(nil, state.Chains[tenv.FeedChainSel].Receiver.Address()) - require.NoError(t, err) - require.Equal(t, twoCoins, balance) -} diff --git a/integration-tests/smoke/ccip/ccip_token_transfer_test.go b/integration-tests/smoke/ccip/ccip_token_transfer_test.go new file mode 100644 index 00000000000..870648508e0 --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_token_transfer_test.go @@ -0,0 +1,245 @@ +package smoke + +import ( + "context" + "math/big" + "testing" + + "golang.org/x/exp/maps" + + "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/crypto" + "github.com/stretchr/testify/require" + + sel "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestTokenTransfer(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := tests.Context(t) + config := &changeset.TestConfigs{} + tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, config) + inMemoryEnv := false + + // use this if you are testing locally in memory + // tenv := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 2, 4, config) + // inMemoryEnv := true + + e := tenv.Env + state, err := changeset.LoadOnchainState(e) + require.NoError(t, err) + + allChainSelectors := maps.Keys(e.Chains) + sourceChain, destChain := allChainSelectors[0], allChainSelectors[1] + ownerSourceChain := e.Chains[sourceChain].DeployerKey + ownerDestChain := e.Chains[destChain].DeployerKey + + oneE18 := new(big.Int).SetUint64(1e18) + funds := new(big.Int).Mul(oneE18, new(big.Int).SetUint64(10)) + + // Deploy and fund self-serve actors + selfServeSrcTokenPoolDeployer := createAndFundSelfServeActor(ctx, t, ownerSourceChain, e.Chains[sourceChain], funds, inMemoryEnv) + selfServeDestTokenPoolDeployer := createAndFundSelfServeActor(ctx, t, ownerDestChain, e.Chains[destChain], funds, inMemoryEnv) + + // Deploy tokens and pool by CCIP Owner + srcToken, _, destToken, _, err := changeset.DeployTransferableToken( + lggr, + tenv.Env.Chains, + sourceChain, + destChain, + ownerSourceChain, + ownerDestChain, + state, + e.ExistingAddresses, + "OWNER_TOKEN", + ) + require.NoError(t, err) + + // Deploy Self Serve tokens and pool + selfServeSrcToken, _, selfServeDestToken, _, err := changeset.DeployTransferableToken( + lggr, + tenv.Env.Chains, + sourceChain, + destChain, + selfServeSrcTokenPoolDeployer, + selfServeDestTokenPoolDeployer, + state, + e.ExistingAddresses, + "SELF_SERVE_TOKEN", + ) + require.NoError(t, err) + require.NoError(t, changeset.AddLanesForAll(e, state)) + + changeset.MintAndAllow(t, e, state, map[uint64]*bind.TransactOpts{ + sourceChain: ownerSourceChain, + destChain: ownerDestChain, + }, map[uint64][]*burn_mint_erc677.BurnMintERC677{ + sourceChain: {srcToken}, + destChain: {destToken}, + }) + changeset.MintAndAllow(t, e, state, map[uint64]*bind.TransactOpts{ + sourceChain: selfServeSrcTokenPoolDeployer, + destChain: selfServeDestTokenPoolDeployer, + }, map[uint64][]*burn_mint_erc677.BurnMintERC677{ + sourceChain: {selfServeSrcToken}, + destChain: {selfServeDestToken}, + }) + + tcs := []struct { + name string + srcChain uint64 + dstChain uint64 + tokenAmounts []router.ClientEVMTokenAmount + receiver common.Address + data []byte + expectedTokenBalances map[common.Address]*big.Int + expectedExecutionState int + }{ + { + name: "Send token to EOA", + srcChain: sourceChain, + dstChain: destChain, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken.Address(), + Amount: oneE18, + }, + }, + receiver: utils.RandomAddress(), + expectedTokenBalances: map[common.Address]*big.Int{ + destToken.Address(): oneE18, + }, + expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS, + }, + { + name: "Send token to contract", + srcChain: sourceChain, + dstChain: destChain, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: srcToken.Address(), + Amount: oneE18, + }, + }, + receiver: state.Chains[destChain].Receiver.Address(), + expectedTokenBalances: map[common.Address]*big.Int{ + destToken.Address(): oneE18, + }, + expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS, + }, + { + name: "Send N tokens to contract", + srcChain: destChain, + dstChain: sourceChain, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: selfServeDestToken.Address(), + Amount: oneE18, + }, + { + Token: destToken.Address(), + Amount: oneE18, + }, + { + Token: selfServeDestToken.Address(), + Amount: oneE18, + }, + }, + receiver: state.Chains[sourceChain].Receiver.Address(), + expectedTokenBalances: map[common.Address]*big.Int{ + selfServeSrcToken.Address(): new(big.Int).Add(oneE18, oneE18), + srcToken.Address(): oneE18, + }, + expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS, + }, + } + + for _, tt := range tcs { + t.Run(tt.name, func(t *testing.T) { + initialBalances := map[common.Address]*big.Int{} + for token := range tt.expectedTokenBalances { + initialBalance := changeset.GetTokenBalance(ctx, t, token, tt.receiver, e.Chains[tt.dstChain]) + initialBalances[token] = initialBalance + } + + changeset.TransferAndWaitForSuccess( + ctx, + t, + e, + state, + tt.srcChain, + tt.dstChain, + tt.tokenAmounts, + tt.receiver, + tt.data, + tt.expectedExecutionState, + ) + + for token, balance := range tt.expectedTokenBalances { + expected := new(big.Int).Add(initialBalances[token], balance) + changeset.WaitForTheTokenBalance(ctx, t, token, tt.receiver, e.Chains[tt.dstChain], expected) + } + }) + } +} + +func createAndFundSelfServeActor( + ctx context.Context, + t *testing.T, + deployer *bind.TransactOpts, + chain deployment.Chain, + amountToFund *big.Int, + isInMemory bool, +) *bind.TransactOpts { + key, err := crypto.GenerateKey() + require.NoError(t, err) + + // Simulated backend sets chainID to 1337 always + chainID := big.NewInt(1337) + if !isInMemory { + // Docker environment runs real geth so chainID has to be set accordingly + stringChainID, err1 := sel.GetChainIDFromSelector(chain.Selector) + require.NoError(t, err1) + chainID, _ = new(big.Int).SetString(stringChainID, 10) + } + + actor, err := bind.NewKeyedTransactorWithChainID(key, chainID) + require.NoError(t, err) + + nonce, err := chain.Client.PendingNonceAt(ctx, deployer.From) + require.NoError(t, err) + + gasPrice, err := chain.Client.SuggestGasPrice(ctx) + require.NoError(t, err) + + tx := types.NewTx(&types.LegacyTx{ + Nonce: nonce, + To: &actor.From, + Value: amountToFund, + Gas: uint64(21000), + GasPrice: gasPrice, + Data: nil, + }) + + signedTx, err := deployer.Signer(deployer.From, tx) + require.NoError(t, err) + + err = chain.Client.SendTransaction(ctx, signedTx) + require.NoError(t, err) + + _, err = chain.Confirm(signedTx) + require.NoError(t, err) + + return actor +} diff --git a/integration-tests/smoke/ccip/ccip_usdc_test.go b/integration-tests/smoke/ccip/ccip_usdc_test.go index c50c2617094..f478392c0f7 100644 --- a/integration-tests/smoke/ccip/ccip_usdc_test.go +++ b/integration-tests/smoke/ccip/ccip_usdc_test.go @@ -3,17 +3,16 @@ package smoke import ( "math/big" "testing" - "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" + "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" @@ -34,6 +33,7 @@ import ( */ func TestUSDCTokenTransfer(t *testing.T) { lggr := logger.TestLogger(t) + ctx := tests.Context(t) config := &changeset.TestConfigs{ IsUSDC: true, } @@ -49,6 +49,10 @@ func TestUSDCTokenTransfer(t *testing.T) { chainC := allChainSelectors[1] chainB := allChainSelectors[2] + ownerChainA := e.Chains[chainA].DeployerKey + ownerChainC := e.Chains[chainC].DeployerKey + ownerChainB := e.Chains[chainB].DeployerKey + aChainUSDC, cChainUSDC, err := changeset.ConfigureUSDCTokenPools(lggr, e.Chains, chainA, chainC, state) require.NoError(t, err) @@ -60,6 +64,8 @@ func TestUSDCTokenTransfer(t *testing.T) { tenv.Env.Chains, chainA, chainC, + ownerChainA, + ownerChainC, state, e.ExistingAddresses, "MY_TOKEN", @@ -69,19 +75,22 @@ func TestUSDCTokenTransfer(t *testing.T) { // Add all lanes require.NoError(t, changeset.AddLanesForAll(e, state)) - mintAndAllow(t, e, state, map[uint64][]*burn_mint_erc677.BurnMintERC677{ - chainA: {aChainUSDC, aChainToken}, - chainB: {bChainUSDC}, - chainC: {cChainUSDC, cChainToken}, - }) - - err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainA], state.Chains[chainA], chainC, aChainUSDC) - require.NoError(t, err) - - err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainB], state.Chains[chainB], chainC, bChainUSDC) - require.NoError(t, err) + changeset.MintAndAllow( + t, + e, + state, + map[uint64]*bind.TransactOpts{ + chainA: ownerChainA, + chainB: ownerChainB, + chainC: ownerChainC, + }, + map[uint64][]*burn_mint_erc677.BurnMintERC677{ + chainA: {aChainUSDC, aChainToken}, + chainB: {bChainUSDC}, + chainC: {cChainUSDC, cChainToken}, + }) - err = changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainC], state.Chains[chainC], chainA, cChainUSDC) + err = updateFeeQuoters(lggr, e, state, chainA, chainB, chainC, aChainUSDC, bChainUSDC, cChainUSDC) require.NoError(t, err) // MockE2EUSDCTransmitter always mint 1, see MockE2EUSDCTransmitter.sol for more details @@ -177,11 +186,12 @@ func TestUSDCTokenTransfer(t *testing.T) { t.Run(tt.name, func(t *testing.T) { initialBalances := map[common.Address]*big.Int{} for token := range tt.expectedTokenBalances { - initialBalance := getTokenBalance(t, token, tt.receiver, e.Chains[tt.destChain]) + initialBalance := changeset.GetTokenBalance(ctx, t, token, tt.receiver, e.Chains[tt.destChain]) initialBalances[token] = initialBalance } - transferAndWaitForSuccess( + changeset.TransferAndWaitForSuccess( + ctx, t, e, state, @@ -195,7 +205,7 @@ func TestUSDCTokenTransfer(t *testing.T) { for token, balance := range tt.expectedTokenBalances { expected := new(big.Int).Add(initialBalances[token], balance) - waitForTheTokenBalance(t, token, tt.receiver, e.Chains[tt.destChain], expected) + changeset.WaitForTheTokenBalance(ctx, t, token, tt.receiver, e.Chains[tt.destChain], expected) } }) } @@ -243,123 +253,26 @@ func TestUSDCTokenTransfer(t *testing.T) { // We sent 1 coin from each source chain, so we should have 2 coins on the destination chain // Receiver is randomly generated so we don't need to get the initial balance first expectedBalance := new(big.Int).Add(tinyOneCoin, tinyOneCoin) - waitForTheTokenBalance(t, cChainUSDC.Address(), receiver, e.Chains[chainC], expectedBalance) + changeset.WaitForTheTokenBalance(ctx, t, cChainUSDC.Address(), receiver, e.Chains[chainC], expectedBalance) }) } -// mintAndAllow mints tokens for deployers and allow router to spend them -func mintAndAllow( - t *testing.T, +func updateFeeQuoters( + lggr logger.Logger, e deployment.Environment, state changeset.CCIPOnChainState, - tkMap map[uint64][]*burn_mint_erc677.BurnMintERC677, -) { - for chain, tokens := range tkMap { - for _, token := range tokens { - twoCoins := new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2)) - - tx, err := token.Mint( - e.Chains[chain].DeployerKey, - e.Chains[chain].DeployerKey.From, - new(big.Int).Mul(twoCoins, big.NewInt(10)), - ) - require.NoError(t, err) - _, err = e.Chains[chain].Confirm(tx) - require.NoError(t, err) - - tx, err = token.Approve(e.Chains[chain].DeployerKey, state.Chains[chain].Router.Address(), twoCoins) - require.NoError(t, err) - _, err = e.Chains[chain].Confirm(tx) - require.NoError(t, err) - } - } -} - -// transferAndWaitForSuccess sends a message from sourceChain to destChain and waits for it to be executed -func transferAndWaitForSuccess( - t *testing.T, - env deployment.Environment, - state changeset.CCIPOnChainState, - sourceChain, destChain uint64, - tokens []router.ClientEVMTokenAmount, - receiver common.Address, - data []byte, - expectedStatus int, -) { - identifier := changeset.SourceDestPair{ - SourceChainSelector: sourceChain, - DestChainSelector: destChain, - } - - startBlocks := make(map[uint64]*uint64) - expectedSeqNum := make(map[changeset.SourceDestPair]uint64) - expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) - - latesthdr, err := env.Chains[destChain].Client.HeaderByNumber(testcontext.Get(t), nil) - require.NoError(t, err) - block := latesthdr.Number.Uint64() - startBlocks[destChain] = &block - - msgSentEvent := changeset.TestSendRequest(t, env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(receiver.Bytes(), 32), - Data: data, - TokenAmounts: tokens, - FeeToken: common.HexToAddress("0x0"), - ExtraArgs: nil, + chainA, chainB, chainC uint64, + aChainUSDC, bChainUSDC, cChainUSDC *burn_mint_erc677.BurnMintERC677, +) error { + updateFeeQtrGrp := errgroup.Group{} + updateFeeQtrGrp.Go(func() error { + return changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainA], state.Chains[chainA], chainC, aChainUSDC) }) - expectedSeqNum[identifier] = msgSentEvent.SequenceNumber - expectedSeqNumExec[identifier] = []uint64{msgSentEvent.SequenceNumber} - - // Wait for all commit reports to land. - changeset.ConfirmCommitForAllWithExpectedSeqNums(t, env, state, expectedSeqNum, startBlocks) - - // Wait for all exec reports to land - states := changeset.ConfirmExecWithSeqNrsForAll(t, env, state, expectedSeqNumExec, startBlocks) - require.Equal(t, expectedStatus, states[identifier][msgSentEvent.SequenceNumber]) -} - -func waitForTheTokenBalance( - t *testing.T, - token common.Address, - receiver common.Address, - chain deployment.Chain, - expected *big.Int, -) { - tokenContract, err := burn_mint_erc677.NewBurnMintERC677(token, chain.Client) - require.NoError(t, err) - - require.Eventually(t, func() bool { - actualBalance, err := tokenContract.BalanceOf(&bind.CallOpts{Context: tests.Context(t)}, receiver) - require.NoError(t, err) - - t.Log("Waiting for the token balance", - "expected", expected, - "actual", actualBalance, - "token", token, - "receiver", receiver, - ) - - return actualBalance.Cmp(expected) == 0 - }, tests.WaitTimeout(t), 100*time.Millisecond) -} - -func getTokenBalance( - t *testing.T, - token common.Address, - receiver common.Address, - chain deployment.Chain, -) *big.Int { - tokenContract, err := burn_mint_erc677.NewBurnMintERC677(token, chain.Client) - require.NoError(t, err) - - balance, err := tokenContract.BalanceOf(&bind.CallOpts{Context: tests.Context(t)}, receiver) - require.NoError(t, err) - - t.Log("Getting token balance", - "actual", balance, - "token", token, - "receiver", receiver, - ) - - return balance + updateFeeQtrGrp.Go(func() error { + return changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainB], state.Chains[chainB], chainC, bChainUSDC) + }) + updateFeeQtrGrp.Go(func() error { + return changeset.UpdateFeeQuoterForUSDC(lggr, e.Chains[chainC], state.Chains[chainC], chainA, cChainUSDC) + }) + return updateFeeQtrGrp.Wait() } diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go deleted file mode 100644 index 918ac243ab8..00000000000 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package smoke - -import ( - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/test-go/testify/require" - "golang.org/x/exp/maps" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -type feeboostTestCase struct { - t *testing.T - sender []byte - deployedEnv changeset.DeployedEnv - onchainState changeset.CCIPOnChainState - initialPrices changeset.InitialPrices - priceFeedPrices priceFeedPrices - sourceChain, destChain uint64 -} - -type priceFeedPrices struct { - linkPrice *big.Int - wethPrice *big.Int -} - -// TODO: find a way to reuse the same test setup for all tests -func Test_CCIPFeeBoosting(t *testing.T) { - setupTestEnv := func(t *testing.T, numChains int) (changeset.DeployedEnv, changeset.CCIPOnChainState, []uint64) { - e, _, _ := testsetups.NewLocalDevEnvironment(t, logger.TestLogger(t), deployment.E18Mult(5), big.NewInt(9e8), nil) - - state, err := changeset.LoadOnchainState(e.Env) - require.NoError(t, err) - - allChainSelectors := maps.Keys(e.Env.Chains) - require.Len(t, allChainSelectors, numChains) - return e, state, allChainSelectors - } - - t.Run("boost needed due to WETH price increase (also covering gas price inscrease)", func(t *testing.T) { - e, state, chains := setupTestEnv(t, 2) - runFeeboostTestCase(feeboostTestCase{ - t: t, - sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), - deployedEnv: e, - onchainState: state, - initialPrices: changeset.InitialPrices{ - LinkPrice: deployment.E18Mult(5), - WethPrice: deployment.E18Mult(9), - GasPrice: changeset.ToPackedFee(big.NewInt(1.8e11), big.NewInt(0)), - }, - priceFeedPrices: priceFeedPrices{ - linkPrice: deployment.E18Mult(5), - wethPrice: big.NewInt(9.9e8), // increase from 9e8 to 9.9e8 - }, - sourceChain: chains[0], - destChain: chains[1], - }) - }) - - t.Run("boost needed due to LINK price decrease", func(t *testing.T) { - e, state, chains := setupTestEnv(t, 2) - runFeeboostTestCase(feeboostTestCase{ - t: t, - sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), - deployedEnv: e, - onchainState: state, - initialPrices: changeset.InitialPrices{ - LinkPrice: deployment.E18Mult(5), - WethPrice: deployment.E18Mult(9), - GasPrice: changeset.ToPackedFee(big.NewInt(1.8e11), big.NewInt(0)), - }, - priceFeedPrices: priceFeedPrices{ - linkPrice: big.NewInt(4.5e18), // decrease from 5e18 to 4.5e18 - wethPrice: big.NewInt(9e8), - }, - sourceChain: chains[0], - destChain: chains[1], - }) - }) -} - -func runFeeboostTestCase(tc feeboostTestCase) { - require.NoError(tc.t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, false)) - - startBlocks := make(map[uint64]*uint64) - expectedSeqNum := make(map[changeset.SourceDestPair]uint64) - expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) - msgSentEvent := changeset.TestSendRequest(tc.t, tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(tc.onchainState.Chains[tc.destChain].Receiver.Address().Bytes(), 32), - Data: []byte("message that needs fee boosting"), - TokenAmounts: nil, - FeeToken: common.HexToAddress("0x0"), - ExtraArgs: nil, - }) - expectedSeqNum[changeset.SourceDestPair{ - SourceChainSelector: tc.sourceChain, - DestChainSelector: tc.destChain, - }] = msgSentEvent.SequenceNumber - expectedSeqNumExec[changeset.SourceDestPair{ - SourceChainSelector: tc.sourceChain, - DestChainSelector: tc.destChain, - }] = []uint64{msgSentEvent.SequenceNumber} - - // hack - time.Sleep(30 * time.Second) - replayBlocks := make(map[uint64]uint64) - replayBlocks[tc.sourceChain] = 1 - replayBlocks[tc.destChain] = 1 - changeset.ReplayLogs(tc.t, tc.deployedEnv.Env.Offchain, replayBlocks) - - changeset.ConfirmCommitForAllWithExpectedSeqNums(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNum, startBlocks) - changeset.ConfirmExecWithSeqNrsForAll(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNumExec, startBlocks) -} diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index 41650f33050..a2c680ee814 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -15,7 +15,6 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" @@ -224,7 +223,13 @@ func NewLocalDevEnvironment( for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock tokenInfo := tokenConfig.GetTokenInfo(env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - ocrParams[chain] = changeset.DefaultOCRParams(feedSel, tokenInfo) + + params := changeset.DefaultOCRParams(feedSel, tokenInfo) + if tCfg.OCRConfigOverride != nil { + params = tCfg.OCRConfigOverride(params) + } + + ocrParams[chain] = params } // Deploy second set of changesets to deploy and configure the CCIP contracts. env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, []commonchangeset.ChangesetApplication{ diff --git a/integration-tests/utils/pgtest/pgtest.go b/integration-tests/utils/pgtest/pgtest.go new file mode 100644 index 00000000000..8b11f9ef424 --- /dev/null +++ b/integration-tests/utils/pgtest/pgtest.go @@ -0,0 +1,35 @@ +package pgtest + +import ( + "testing" + + "github.com/google/uuid" + "github.com/jmoiron/sqlx" + "github.com/scylladb/go-reflectx" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + "github.com/smartcontractkit/chainlink-common/pkg/utils" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/store/dialects" +) + +func NewSqlxDB(t testing.TB) *sqlx.DB { + db, err := sqlx.Open(string(dialects.TransactionWrappedPostgres), uuid.New().String()) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, db.Close()) }) + db.MapperFunc(reflectx.CamelToSnakeASCII) + + return db +} + +func MustExec(t *testing.T, ds sqlutil.DataSource, stmt string, args ...interface{}) { + ctx := tests.Context(t) + require.NoError(t, utils.JustError(ds.ExecContext(ctx, stmt, args...))) +} + +func MustCount(t *testing.T, db *sqlx.DB, stmt string, args ...interface{}) (cnt int) { + require.NoError(t, db.Get(&cnt, stmt, args...)) + return +} diff --git a/integration-tests/utils/pgtest/txdb.go b/integration-tests/utils/pgtest/txdb.go new file mode 100644 index 00000000000..f28b6f95f2b --- /dev/null +++ b/integration-tests/utils/pgtest/txdb.go @@ -0,0 +1,510 @@ +package pgtest + +import ( + "context" + "database/sql" + "database/sql/driver" + "flag" + "fmt" + "io" + "net/url" + "strings" + "sync" + "testing" + + "github.com/jmoiron/sqlx" + "go.uber.org/multierr" + + "github.com/smartcontractkit/chainlink/v2/core/config/env" + "github.com/smartcontractkit/chainlink/v2/core/store/dialects" +) + +// txdb is a simplified version of https://github.com/DATA-DOG/go-txdb +// +// The original lib has various problems and is hard to understand because it +// tries to be more general. The version in this file is more tightly focused +// to our needs and should be easier to reason about and less likely to have +// subtle bugs/races. +// +// It doesn't currently support savepoints but could be made to if necessary. +// +// Transaction BEGIN/ROLLBACK effectively becomes a no-op, this should have no +// negative impact on normal test operation. +// +// If you MUST test BEGIN/ROLLBACK behaviour, you will have to configure your +// store to use the raw DialectPostgres dialect and setup a one-use database. +// See heavyweight.FullTestDB() as a convenience function to help you do this, +// but please use sparingly because as it's name implies, it is expensive. +func init() { + testing.Init() + if !flag.Parsed() { + flag.Parse() + } + if testing.Short() { + // -short tests don't need a DB + return + } + dbURL := string(env.DatabaseURL.Get()) + if dbURL == "" { + panic("you must provide a CL_DATABASE_URL environment variable") + } + + parsed, err := url.Parse(dbURL) + if err != nil { + panic(err) + } + if parsed.Path == "" { + msg := fmt.Sprintf("invalid %[1]s: `%[2]s`. You must set %[1]s env var to point to your test database. Note that the test database MUST end in `_test` to differentiate from a possible production DB. HINT: Try %[1]s=postgresql://postgres@localhost:5432/chainlink_test?sslmode=disable", env.DatabaseURL, parsed.String()) + panic(msg) + } + if !strings.HasSuffix(parsed.Path, "_test") { + msg := fmt.Sprintf("cannot run tests against database named `%s`. Note that the test database MUST end in `_test` to differentiate from a possible production DB. HINT: Try %s=postgresql://postgres@localhost:5432/chainlink_test?sslmode=disable", parsed.Path[1:], env.DatabaseURL) + panic(msg) + } + name := string(dialects.TransactionWrappedPostgres) + sql.Register(name, &txDriver{ + dbURL: dbURL, + conns: make(map[string]*conn), + }) + sqlx.BindDriver(name, sqlx.DOLLAR) +} + +var _ driver.Conn = &conn{} + +var _ driver.Validator = &conn{} +var _ driver.SessionResetter = &conn{} + +// txDriver is an sql driver which runs on a single transaction. +// When `Close` is called, transaction is rolled back. +type txDriver struct { + sync.Mutex + db *sql.DB + conns map[string]*conn + + dbURL string +} + +func (d *txDriver) Open(dsn string) (driver.Conn, error) { + d.Lock() + defer d.Unlock() + // Open real db connection if its the first call + if d.db == nil { + db, err := sql.Open(string(dialects.Postgres), d.dbURL) + if err != nil { + return nil, err + } + d.db = db + } + c, exists := d.conns[dsn] + if !exists || !c.tryOpen() { + tx, err := d.db.Begin() + if err != nil { + return nil, err + } + c = &conn{tx: tx, opened: 1, dsn: dsn} + c.removeSelf = func() error { + return d.deleteConn(c) + } + d.conns[dsn] = c + } + return c, nil +} + +// deleteConn is called by a connection when it is closed via the `close` method. +// It also auto-closes the DB when the last checked out connection is closed. +func (d *txDriver) deleteConn(c *conn) error { + // must lock here to avoid racing with Open + d.Lock() + defer d.Unlock() + + if d.conns[c.dsn] != c { + return nil // already been replaced + } + delete(d.conns, c.dsn) + if len(d.conns) == 0 && d.db != nil { + if err := d.db.Close(); err != nil { + return err + } + d.db = nil + } + return nil +} + +type conn struct { + sync.Mutex + dsn string + tx *sql.Tx // tx may be shared by many conns, definitive one lives in the map keyed by DSN on the txDriver. Do not modify from conn + closed bool + opened int + removeSelf func() error +} + +func (c *conn) Begin() (driver.Tx, error) { + c.Lock() + defer c.Unlock() + if c.closed { + panic("conn is closed") + } + // Begin is a noop because the transaction was already opened + return tx{c.tx}, nil +} + +// Implement the "ConnBeginTx" interface +func (c *conn) BeginTx(_ context.Context, opts driver.TxOptions) (driver.Tx, error) { + // Context is ignored, because single transaction is shared by all callers, thus caller should not be able to + // control it with local context + return c.Begin() +} + +// Prepare returns a prepared statement, bound to this connection. +func (c *conn) Prepare(query string) (driver.Stmt, error) { + return c.PrepareContext(context.Background(), query) +} + +// Implement the "ConnPrepareContext" interface +func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + c.Lock() + defer c.Unlock() + if c.closed { + panic("conn is closed") + } + + // TODO: Fix context handling + // FIXME: It is not safe to give the passed in context to the tx directly + // because the tx is shared by many conns and cancelling the context will + // destroy the tx which can affect other conns + st, err := c.tx.PrepareContext(context.Background(), query) + if err != nil { + return nil, err + } + return &stmt{st, c}, nil +} + +// IsValid is called prior to placing the connection into the +// connection pool by database/sql. The connection will be discarded if false is returned. +func (c *conn) IsValid() bool { + c.Lock() + defer c.Unlock() + return !c.closed +} + +func (c *conn) ResetSession(ctx context.Context) error { + // Ensure bad connections are reported: From database/sql/driver: + // If a connection is never returned to the connection pool but immediately reused, then + // ResetSession is called prior to reuse but IsValid is not called. + c.Lock() + defer c.Unlock() + if c.closed { + return driver.ErrBadConn + } + + return nil +} + +// pgx returns nil +func (c *conn) CheckNamedValue(nv *driver.NamedValue) error { + return nil +} + +// Implement the "QueryerContext" interface +func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { + c.Lock() + defer c.Unlock() + if c.closed { + panic("conn is closed") + } + + // TODO: Fix context handling + rs, err := c.tx.QueryContext(context.Background(), query, mapNamedArgs(args)...) + if err != nil { + return nil, err + } + defer rs.Close() + + return buildRows(rs) +} + +// Implement the "ExecerContext" interface +func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + c.Lock() + defer c.Unlock() + if c.closed { + panic("conn is closed") + } + // TODO: Fix context handling + return c.tx.ExecContext(context.Background(), query, mapNamedArgs(args)...) +} + +// tryOpen attempts to increment the open count, but returns false if closed. +func (c *conn) tryOpen() bool { + c.Lock() + defer c.Unlock() + if c.closed { + return false + } + c.opened++ + return true +} + +// Close invalidates and potentially stops any current +// prepared statements and transactions, marking this +// connection as no longer in use. +// +// Because the sql package maintains a free pool of +// connections and only calls Close when there's a surplus of +// idle connections, it shouldn't be necessary for drivers to +// do their own connection caching. +// +// Drivers must ensure all network calls made by Close +// do not block indefinitely (e.g. apply a timeout). +func (c *conn) Close() (err error) { + if !c.close() { + return + } + // Wait to remove self to avoid nesting locks. + if err := c.removeSelf(); err != nil { + panic(err) + } + return +} + +//nolint:revive +func (c *conn) close() bool { + c.Lock() + defer c.Unlock() + if c.closed { + // Double close, should be a safe to make this a noop + // PGX allows double close + // See: https://github.com/jackc/pgx/blob/a457da8bffa4f90ad672fa093ee87f20cf06687b/conn.go#L249 + return false + } + + c.opened-- + if c.opened > 0 { + return false + } + if c.tx != nil { + if err := c.tx.Rollback(); err != nil { + panic(err) + } + c.tx = nil + } + c.closed = true + return true +} + +type tx struct { + tx *sql.Tx +} + +func (tx tx) Commit() error { + // Commit is a noop because the transaction will be rolled back at the end + return nil +} + +func (tx tx) Rollback() error { + // Rollback is a noop because the transaction will be rolled back at the end + return nil +} + +type stmt struct { + st *sql.Stmt + conn *conn +} + +func (s stmt) Exec(args []driver.Value) (driver.Result, error) { + s.conn.Lock() + defer s.conn.Unlock() + if s.conn.closed { + panic("conn is closed") + } + return s.st.Exec(mapArgs(args)...) +} + +// Implement the "StmtExecContext" interface +func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + s.conn.Lock() + defer s.conn.Unlock() + if s.conn.closed { + panic("conn is closed") + } + // TODO: Fix context handling + return s.st.ExecContext(context.Background(), mapNamedArgs(args)...) +} + +func mapArgs(args []driver.Value) (res []interface{}) { + res = make([]interface{}, len(args)) + for i := range args { + res[i] = args[i] + } + return +} + +func (s stmt) NumInput() int { + return -1 +} + +func (s stmt) Query(args []driver.Value) (driver.Rows, error) { + s.conn.Lock() + defer s.conn.Unlock() + if s.conn.closed { + panic("conn is closed") + } + rows, err := s.st.Query(mapArgs(args)...) + defer func() { + err = multierr.Combine(err, rows.Close()) + }() + if err != nil { + return nil, err + } + return buildRows(rows) +} + +// Implement the "StmtQueryContext" interface +func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + s.conn.Lock() + defer s.conn.Unlock() + if s.conn.closed { + panic("conn is closed") + } + // TODO: Fix context handling + rows, err := s.st.QueryContext(context.Background(), mapNamedArgs(args)...) + if err != nil { + return nil, err + } + return buildRows(rows) +} + +func (s stmt) Close() error { + s.conn.Lock() + defer s.conn.Unlock() + return s.st.Close() +} + +func buildRows(r *sql.Rows) (driver.Rows, error) { + set := &rowSets{} + rs := &rows{} + if err := rs.read(r); err != nil { + return set, err + } + set.sets = append(set.sets, rs) + for r.NextResultSet() { + rss := &rows{} + if err := rss.read(r); err != nil { + return set, err + } + set.sets = append(set.sets, rss) + } + return set, nil +} + +// Implement the "RowsNextResultSet" interface +func (rs *rowSets) HasNextResultSet() bool { + return rs.pos+1 < len(rs.sets) +} + +// Implement the "RowsNextResultSet" interface +func (rs *rowSets) NextResultSet() error { + if !rs.HasNextResultSet() { + return io.EOF + } + + rs.pos++ + return nil +} + +type rows struct { + rows [][]driver.Value + pos int + cols []string + colTypes []*sql.ColumnType +} + +func (r *rows) Columns() []string { + return r.cols +} + +func (r *rows) ColumnTypeDatabaseTypeName(index int) string { + return r.colTypes[index].DatabaseTypeName() +} + +func (r *rows) Next(dest []driver.Value) error { + r.pos++ + if r.pos > len(r.rows) { + return io.EOF + } + + for i, val := range r.rows[r.pos-1] { + dest[i] = *(val.(*interface{})) + } + + return nil +} + +func (r *rows) Close() error { + return nil +} + +func (r *rows) read(rs *sql.Rows) error { + var err error + r.cols, err = rs.Columns() + if err != nil { + return err + } + + r.colTypes, err = rs.ColumnTypes() + if err != nil { + return err + } + + for rs.Next() { + values := make([]interface{}, len(r.cols)) + for i := range values { + values[i] = new(interface{}) + } + if err := rs.Scan(values...); err != nil { + return err + } + row := make([]driver.Value, len(r.cols)) + for i, v := range values { + row[i] = driver.Value(v) + } + r.rows = append(r.rows, row) + } + return rs.Err() +} + +type rowSets struct { + sets []*rows + pos int +} + +func (rs *rowSets) Columns() []string { + return rs.sets[rs.pos].cols +} + +func (rs *rowSets) ColumnTypeDatabaseTypeName(index int) string { + return rs.sets[rs.pos].ColumnTypeDatabaseTypeName(index) +} + +func (rs *rowSets) Close() error { + return nil +} + +// advances to next row +func (rs *rowSets) Next(dest []driver.Value) error { + return rs.sets[rs.pos].Next(dest) +} + +func mapNamedArgs(args []driver.NamedValue) (res []interface{}) { + res = make([]interface{}, len(args)) + for i := range args { + name := args[i].Name + if name != "" { + res[i] = sql.Named(name, args[i].Value) + } else { + res[i] = args[i].Value + } + } + return +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5458e7e0f9f..492624a2a6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,20 +20,16 @@ importers: packages: - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/runtime@7.25.6': - resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} '@changesets/apply-release-plan@6.1.4': @@ -297,8 +293,8 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + es-abstract@1.23.5: + resolution: {integrity: sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==} engines: {node: '>= 0.4'} es-define-property@1.0.0: @@ -320,8 +316,8 @@ packages: es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} escalade@3.2.0: @@ -483,6 +479,10 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -514,14 +514,26 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.0: + resolution: {integrity: sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -542,6 +554,10 @@ packages: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + is-shared-array-buffer@1.0.3: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} @@ -562,9 +578,17 @@ packages: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -661,8 +685,8 @@ packages: normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + object-inspect@1.13.3: + resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} engines: {node: '>= 0.4'} object-keys@1.1.1: @@ -723,8 +747,8 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -777,11 +801,15 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + reflect.getprototypeof@1.0.7: + resolution: {integrity: sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==} + engines: {node: '>= 0.4'} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + regexp.prototype.flags@1.5.3: + resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} require-directory@2.1.1: @@ -965,12 +993,12 @@ packages: resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} engines: {node: '>= 0.4'} - typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + typed-array-byte-offset@1.0.3: + resolution: {integrity: sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==} engines: {node: '>= 0.4'} - typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} unbox-primitive@1.0.2: @@ -995,6 +1023,14 @@ packages: which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + which-builtin-type@1.2.0: + resolution: {integrity: sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} @@ -1002,8 +1038,8 @@ packages: resolution: {integrity: sha512-MOiaDbA5ZZgUjkeMWM5EkJp4loW5ZRoa5bc3/aeMox/PJelMhE6t7S/mLuiY43DBupyxH+S0U1bTui9kWUlmsw==} engines: {node: '>=8.15'} - which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + which-typed-array@1.1.16: + resolution: {integrity: sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==} engines: {node: '>= 0.4'} which@1.3.1: @@ -1050,27 +1086,21 @@ packages: snapshots: - '@babel/code-frame@7.24.7': - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.1.0 - - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/highlight@7.24.7': + '@babel/code-frame@7.26.2': dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 + '@babel/helper-validator-identifier': 7.25.9 js-tokens: 4.0.0 - picocolors: 1.1.0 + picocolors: 1.1.1 - '@babel/runtime@7.25.6': + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 '@changesets/apply-release-plan@6.1.4': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/config': 2.3.1 '@changesets/get-version-range-type': 0.3.2 '@changesets/git': 2.0.0 @@ -1086,7 +1116,7 @@ snapshots: '@changesets/assemble-release-plan@5.2.4': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/errors': 0.1.4 '@changesets/get-dependents-graph': 1.3.6 '@changesets/types': 5.2.1 @@ -1099,7 +1129,7 @@ snapshots: '@changesets/cli@2.26.2': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/apply-release-plan': 6.1.4 '@changesets/assemble-release-plan': 5.2.4 '@changesets/changelog-git': 0.1.14 @@ -1164,7 +1194,7 @@ snapshots: '@changesets/get-release-plan@3.0.17': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/assemble-release-plan': 5.2.4 '@changesets/config': 2.3.1 '@changesets/pre': 1.0.14 @@ -1176,7 +1206,7 @@ snapshots: '@changesets/git@2.0.0': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 @@ -1195,7 +1225,7 @@ snapshots: '@changesets/pre@1.0.14': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/errors': 0.1.4 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 @@ -1203,7 +1233,7 @@ snapshots: '@changesets/read@0.5.9': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/git': 2.0.0 '@changesets/logger': 0.0.5 '@changesets/parse': 0.3.16 @@ -1218,7 +1248,7 @@ snapshots: '@changesets/write@0.2.3': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/types': 5.2.1 fs-extra: 7.0.1 human-id: 1.0.2 @@ -1226,14 +1256,14 @@ snapshots: '@manypkg/find-root@1.1.0': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 '@manypkg/get-packages@1.1.3': dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -1291,7 +1321,7 @@ snapshots: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.23.5 es-shim-unscopables: 1.0.2 arraybuffer.prototype.slice@1.0.3: @@ -1299,7 +1329,7 @@ snapshots: array-buffer-byte-length: 1.0.1 call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.23.5 es-errors: 1.3.0 get-intrinsic: 1.2.4 is-array-buffer: 3.0.4 @@ -1459,7 +1489,7 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract@1.23.3: + es-abstract@1.23.5: dependencies: array-buffer-byte-length: 1.0.1 arraybuffer.prototype.slice: 1.0.3 @@ -1472,7 +1502,7 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.0.0 es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 + es-to-primitive: 1.3.0 function.prototype.name: 1.1.6 get-intrinsic: 1.2.4 get-symbol-description: 1.0.2 @@ -1492,10 +1522,10 @@ snapshots: is-string: 1.0.7 is-typed-array: 1.1.13 is-weakref: 1.0.2 - object-inspect: 1.13.2 + object-inspect: 1.13.3 object-keys: 1.1.1 object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 + regexp.prototype.flags: 1.5.3 safe-array-concat: 1.1.2 safe-regex-test: 1.0.3 string.prototype.trim: 1.2.9 @@ -1503,10 +1533,10 @@ snapshots: string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.2 typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.6 + typed-array-byte-offset: 1.0.3 + typed-array-length: 1.0.7 unbox-primitive: 1.0.2 - which-typed-array: 1.1.15 + which-typed-array: 1.1.16 es-define-property@1.0.0: dependencies: @@ -1528,7 +1558,7 @@ snapshots: dependencies: hasown: 2.0.2 - es-to-primitive@1.2.1: + es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 is-date-object: 1.0.5 @@ -1601,7 +1631,7 @@ snapshots: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.23.5 functions-have-names: 1.2.3 functions-have-names@1.2.3: {} @@ -1697,6 +1727,10 @@ snapshots: is-arrayish@0.2.1: {} + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + is-bigint@1.0.4: dependencies: has-bigints: 1.0.2 @@ -1726,12 +1760,22 @@ snapshots: is-extglob@2.1.1: {} + is-finalizationregistry@1.1.0: + dependencies: + call-bind: 1.0.7 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-map@2.0.3: {} + is-negative-zero@2.0.3: {} is-number-object@1.0.7: @@ -1747,6 +1791,8 @@ snapshots: call-bind: 1.0.7 has-tostringtag: 1.0.2 + is-set@2.0.3: {} + is-shared-array-buffer@1.0.3: dependencies: call-bind: 1.0.7 @@ -1765,12 +1811,19 @@ snapshots: is-typed-array@1.1.13: dependencies: - which-typed-array: 1.1.15 + which-typed-array: 1.1.16 + + is-weakmap@2.0.2: {} is-weakref@1.0.2: dependencies: call-bind: 1.0.7 + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + is-windows@1.0.2: {} isarray@2.0.5: {} @@ -1864,7 +1917,7 @@ snapshots: semver: 5.7.2 validate-npm-package-license: 3.0.4 - object-inspect@1.13.2: {} + object-inspect@1.13.3: {} object-keys@1.1.1: {} @@ -1905,7 +1958,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -1916,7 +1969,7 @@ snapshots: path-type@4.0.0: {} - picocolors@1.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -1968,9 +2021,19 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + reflect.getprototypeof@1.0.7: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.5 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + which-builtin-type: 1.2.0 + regenerator-runtime@0.14.1: {} - regexp.prototype.flags@1.5.2: + regexp.prototype.flags@1.5.3: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 @@ -2043,7 +2106,7 @@ snapshots: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 - object-inspect: 1.13.2 + object-inspect: 1.13.3 signal-exit@3.0.7: {} @@ -2093,7 +2156,7 @@ snapshots: dependencies: call-bind: 1.0.7 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.23.5 es-object-atoms: 1.0.0 string.prototype.trimend@1.0.8: @@ -2172,7 +2235,7 @@ snapshots: has-proto: 1.0.3 is-typed-array: 1.1.13 - typed-array-byte-offset@1.0.2: + typed-array-byte-offset@1.0.3: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 @@ -2180,15 +2243,16 @@ snapshots: gopd: 1.0.1 has-proto: 1.0.3 is-typed-array: 1.1.13 + reflect.getprototypeof: 1.0.7 - typed-array-length@1.0.6: + typed-array-length@1.0.7: dependencies: call-bind: 1.0.7 for-each: 0.3.3 gopd: 1.0.1 - has-proto: 1.0.3 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 + reflect.getprototypeof: 1.0.7 unbox-primitive@1.0.2: dependencies: @@ -2223,6 +2287,29 @@ snapshots: is-string: 1.0.7 is-symbol: 1.0.4 + which-builtin-type@1.2.0: + dependencies: + call-bind: 1.0.7 + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.1.0 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.16 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + which-module@2.0.1: {} which-pm@2.2.0: @@ -2230,7 +2317,7 @@ snapshots: load-yaml-file: 0.2.0 path-exists: 4.0.0 - which-typed-array@1.1.15: + which-typed-array@1.1.16: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.7 diff --git a/tools/bin/go_core_tests b/tools/bin/go_core_tests index 88ee82c9261..76c15fccd07 100755 --- a/tools/bin/go_core_tests +++ b/tools/bin/go_core_tests @@ -4,7 +4,7 @@ set +e SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"` OUTPUT_FILE=${OUTPUT_FILE:-"./output.txt"} -EXTRA_FLAGS="" +EXTRA_FLAGS="-timeout 20m" echo "Test execution results: ---------------------" echo ""