diff --git a/.changeset/popular-rules-live.md b/.changeset/popular-rules-live.md new file mode 100644 index 00000000000..2d996a28dc2 --- /dev/null +++ b/.changeset/popular-rules-live.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Fixes a race condition with the Finalizer when clearing txs #bugfix diff --git a/.changeset/spotty-knives-smile.md b/.changeset/spotty-knives-smile.md new file mode 100644 index 00000000000..8389b72414c --- /dev/null +++ b/.changeset/spotty-knives-smile.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Increase GasLimit for Automation on ZKsync to 6M #nops diff --git a/.changeset/tricky-clouds-move.md b/.changeset/tricky-clouds-move.md new file mode 100644 index 00000000000..8cb50dbb048 --- /dev/null +++ b/.changeset/tricky-clouds-move.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Updated Solana TXM to store prebroadcast transaction errors caught upfront by simulation. Refactored error parsing to more easily introduce new error cases. Optimized storing finalized and errored transaction to minimize memory usage. #updated diff --git a/.github/actions/version-file-bump/action.yml b/.github/actions/version-file-bump/action.yml index eb8d5c17426..17bdc71a716 100644 --- a/.github/actions/version-file-bump/action.yml +++ b/.github/actions/version-file-bump/action.yml @@ -1,5 +1,5 @@ name: version-file-bump -description: "Ensure that the VERSION file has been bumped since the last release." +description: "Ensure that the package.json version field has been bumped since the last release." inputs: github-token: description: "Github access token" diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index 12cfcfb9256..66d19dfaf0d 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -987,6 +987,45 @@ runner-test-matrix: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3 E2E_JD_VERSION: 0.6.0 + - id: smoke/ccip/ccip_message_limitations_test.go:* + path: integration-tests/smoke/ccip/ccip_message_limitations_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 -run '^Test_CCIPMessageLimitations' -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_JD_VERSION: 0.6.0 + + - id: smoke/ccip/ccip_token_price_updates_test.go:* + path: integration-tests/smoke/ccip/ccip_token_price_updates_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_token_price_updates_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_JD_VERSION: 0.6.0 + + - id: smoke/ccip/ccip_gas_price_updates_test.go:* + path: integration-tests/smoke/ccip/ccip_gas_price_updates_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_gas_price_updates_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_JD_VERSION: 0.6.0 + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_TwoMessagesOnTwoLanesIncludingBatching$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker diff --git a/.github/workflows/build-publish-develop-pr.yml b/.github/workflows/build-publish-develop-pr.yml index 68075422adf..92d9e0445a6 100644 --- a/.github/workflows/build-publish-develop-pr.yml +++ b/.github/workflows/build-publish-develop-pr.yml @@ -2,9 +2,9 @@ name: "Build and Publish GoReleaser" on: pull_request: - # The default types are opened, synchronize, and reopened - # See https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request - # We add a label trigger too, since when the build-publish label is added to a PR, we want to build and publish + # The default types are opened, synchronize, and reopened + # See https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request + # We add a label trigger too, since when the build-publish label is added to a PR, we want to build and publish types: - opened - synchronize @@ -28,49 +28,41 @@ env: # a commit is pushed to develop before merge is run. CHECKOUT_REF: ${{ github.event.inputs.git_ref || github.sha }} - jobs: - merge: + image-tag: runs-on: ubuntu-latest - needs: [split, image-tag] - if: ${{ needs.image-tag.outputs.release-type == 'nightly' }} - permissions: - id-token: write - contents: read + outputs: + image-tag: ${{ steps.get-image-tag.outputs.image-tag }} + release-type: ${{ steps.get-image-tag.outputs.release-type }} steps: - name: Checkout repository uses: actions/checkout@v4.2.1 with: ref: ${{ env.CHECKOUT_REF }} - - name: Configure aws credentials - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 - with: - role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_BUILD_PUBLISH_DEVELOP_PR }} - aws-region: ${{ secrets.AWS_REGION }} - mask-aws-account-id: true - role-session-name: "merge" - - - uses: actions/cache/restore@v4.1.1 - with: - path: dist/linux_amd64_v1 - key: chainlink-amd64-${{ github.sha }} - fail-on-cache-miss: true - - - uses: actions/cache/restore@v4.1.1 - with: - path: dist/linux_arm64_v8.0 - key: chainlink-arm64-${{ github.sha }} - fail-on-cache-miss: true - - - name: Merge images for both architectures - uses: ./.github/actions/goreleaser-build-sign-publish - with: - docker-registry: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }} - docker-image-tag: ${{ needs.image-tag.outputs.image-tag }} - goreleaser-release-type: "merge" - goreleaser-config: .goreleaser.develop.yaml - goreleaser-key: ${{ secrets.GORELEASER_KEY }} + - name: Get image tag + id: get-image-tag + run: | + short_sha=$(git rev-parse --short HEAD) + echo "release-type=snapshot" | tee -a $GITHUB_OUTPUT + if [[ ${{ github.event_name }} == 'push' ]]; then + echo "image-tag=develop" | tee -a $GITHUB_OUTPUT + echo "release-type=nightly" | tee -a $GITHUB_OUTPUT + elif [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then + echo "image-tag=${short_sha}" | tee -a $GITHUB_OUTPUT + if [[ "${{ inputs.build-publish }}" == 'false' ]]; then + echo "release-type=snapshot" | tee -a $GITHUB_OUTPUT + else + echo "release-type=nightly" | tee -a $GITHUB_OUTPUT + fi + else + if [[ ${{ github.event_name }} == "pull_request" ]]; then + echo "image-tag=pr-${{ github.event.number }}-${short_sha}" | tee -a $GITHUB_OUTPUT + if [[ ${{ contains(github.event.pull_request.labels.*.name, 'build-publish') }} == "true" ]]; then + echo "release-type=nightly" | tee -a $GITHUB_OUTPUT + fi + fi + fi split: name: "split-${{ matrix.goarch }}" @@ -113,7 +105,7 @@ jobs: - name: Build images for ${{ matrix.goarch }} uses: ./.github/actions/goreleaser-build-sign-publish - if: steps.cache.outputs.cache-hit != 'true' + if: github.event_name == 'workflow_dispatch' || steps.cache.outputs.cache-hit != 'true' with: docker-registry: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }} docker-image-tag: ${{ needs.image-tag.outputs.image-tag }} @@ -121,37 +113,44 @@ jobs: goreleaser-config: .goreleaser.develop.yaml goreleaser-key: ${{ secrets.GORELEASER_KEY }} - image-tag: + merge: runs-on: ubuntu-latest - outputs: - image-tag: ${{ steps.get-image-tag.outputs.image-tag }} - release-type: ${{ steps.get-image-tag.outputs.release-type }} + needs: [split, image-tag] + if: ${{ needs.image-tag.outputs.release-type == 'nightly' }} + permissions: + id-token: write + contents: read steps: - name: Checkout repository uses: actions/checkout@v4.2.1 with: ref: ${{ env.CHECKOUT_REF }} - - name: Get image tag - id: get-image-tag - run: | - short_sha=$(git rev-parse --short HEAD) - echo "release-type=snapshot" | tee -a $GITHUB_OUTPUT - if [[ ${{ github.event_name }} == 'push' ]]; then - echo "image-tag=develop" | tee -a $GITHUB_OUTPUT - echo "release-type=nightly" | tee -a $GITHUB_OUTPUT - elif [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then - echo "image-tag=${short_sha}" | tee -a $GITHUB_OUTPUT - if [[ "${{ inputs.build-publish }}" == 'false' ]]; then - echo "release-type=snapshot" | tee -a $GITHUB_OUTPUT - else - echo "release-type=nightly" | tee -a $GITHUB_OUTPUT - fi - else - if [[ ${{ github.event_name }} == "pull_request" ]]; then - echo "image-tag=pr-${{ github.event.number }}-${short_sha}" | tee -a $GITHUB_OUTPUT - if [[ ${{ contains(github.event.pull_request.labels.*.name, 'build-publish') }} == "true" ]]; then - echo "release-type=nightly" | tee -a $GITHUB_OUTPUT - fi - fi - fi + - name: Configure aws credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_OIDC_IAM_ROLE_BUILD_PUBLISH_DEVELOP_PR }} + aws-region: ${{ secrets.AWS_REGION }} + mask-aws-account-id: true + role-session-name: "merge" + + - uses: actions/cache/restore@v4.1.1 + with: + path: dist/linux_amd64_v1 + key: chainlink-amd64-${{ github.sha }} + fail-on-cache-miss: true + + - uses: actions/cache/restore@v4.1.1 + with: + path: dist/linux_arm64_v8.0 + key: chainlink-arm64-${{ github.sha }} + fail-on-cache-miss: true + + - name: Merge images for both architectures + uses: ./.github/actions/goreleaser-build-sign-publish + with: + docker-registry: ${{ secrets.AWS_SDLC_ECR_HOSTNAME }} + docker-image-tag: ${{ needs.image-tag.outputs.image-tag }} + goreleaser-release-type: "merge" + goreleaser-config: .goreleaser.develop.yaml + goreleaser-key: ${{ secrets.GORELEASER_KEY }} diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 3d7e925dba0..2889ee5e5ea 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -7,18 +7,48 @@ on: env: ECR_HOSTNAME: public.ecr.aws - ECR_IMAGE_NAME: chainlink/chainlink jobs: checks: name: "Checks" runs-on: ubuntu-20.04 + outputs: + git-tag-type: ${{ steps.check-git-tag-type.outputs.git-tag-type }} + ecr-image-name: ${{ steps.check-git-tag-type.outputs.ecr-image-name }} steps: - name: Checkout repository uses: actions/checkout@v4.2.1 + - name: Check git tag type + id: check-git-tag-type + shell: bash + env: + GIT_TAG: ${{ github.ref_name}} + run: | + # Check if git tag is related to CCIP + # Should match: + # v1.0.0-ccip1.0.0-beta.1 + # v1.0.0-ccip1.0.0-rc.0 + # v1.0.0-ccip1.0.0 + if [[ $GIT_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+-ccip[0-9]+\.[0-9]+\.[0-9]+(-((beta|rc)\.[0-9]+))?$ ]]; then + echo "git-tag-type=ccip" | tee -a "$GITHUB_OUTPUT" + echo "ecr-image-name=chainlink/ccip" | tee -a "$GITHUB_OUTPUT" + else + echo "git-tag-type=core" | tee -a "$GITHUB_OUTPUT" + echo "ecr-image-name=chainlink/chainlink" | tee -a "$GITHUB_OUTPUT" + fi + - name: Fail if CCIP release has wrong version + if: ${{ steps.check-git-tag-type.outputs.git-tag-type == 'ccip' }} + run: | + version=$(jq -r '.version' ./package.json) + echo "Package version: $version" + echo "Git tag type: ${{ steps.check-git-tag-type.outputs.git-tag-type }}" + if [[ $version != *"-ccip"* ]]; then + echo "Error: Version '$version' does not match required CCIP format." + exit 1 + fi - name: Check for VERSION file bump on tags - # Avoids checking VERSION file bump on forks. - if: ${{ github.repository == 'smartcontractkit/chainlink' }} + # Avoids checking VERSION file bump on forks or from CCIP releases. + if: ${{ github.repository == 'smartcontractkit/chainlink' && steps.check-git-tag-type.outputs.git-tag-type == 'core' }} uses: ./.github/actions/version-file-bump with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -47,7 +77,7 @@ jobs: aws-role-duration-seconds: ${{ secrets.AWS_ROLE_DURATION_SECONDS }} aws-region: ${{ secrets.AWS_REGION }} ecr-hostname: ${{ env.ECR_HOSTNAME }} - ecr-image-name: ${{ env.ECR_IMAGE_NAME }} + ecr-image-name: ${{ needs.checks.outputs.ecr-image-name }} dockerhub_username: ${{ secrets.DOCKERHUB_READONLY_USERNAME }} dockerhub_password: ${{ secrets.DOCKERHUB_READONLY_PASSWORD }} sign-images: true @@ -57,13 +87,13 @@ jobs: uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 with: subject-digest: ${{ steps.build-sign-publish.outputs.docker-image-digest }} - subject-name: ${{ env.ECR_HOSTNAME }}/${{ env.ECR_IMAGE_NAME }} + subject-name: ${{ env.ECR_HOSTNAME }}/${{ needs.checks.outputs.ecr-image-name }} push-to-registry: true # Notify Slack channel for new git tags. slack-notify: if: github.ref_type == 'tag' - needs: [build-sign-publish-chainlink] + needs: [checks, build-sign-publish-chainlink] runs-on: ubuntu-24.04 environment: build-publish steps: @@ -91,7 +121,7 @@ jobs: format( '{0}/{1}:{2}', env.ECR_HOSTNAME, - env.ECR_IMAGE_NAME, + needs.checks.outputs.ecr-image-name, needs.build-sign-publish-chainlink.outputs.docker-image-tag ) || '' }} diff --git a/.mockery.yaml b/.mockery.yaml index 70b7a9947f6..1cb8c375ba0 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -234,6 +234,7 @@ packages: config: filename: starknet.go VRF: + Workflow: github.com/smartcontractkit/chainlink/v2/core/services/ocr: interfaces: OCRContractTrackerDB: diff --git a/contracts/.changeset/bright-jokes-kiss.md b/contracts/.changeset/bright-jokes-kiss.md new file mode 100644 index 00000000000..9aac95d84c9 --- /dev/null +++ b/contracts/.changeset/bright-jokes-kiss.md @@ -0,0 +1,10 @@ +--- +'@chainlink/contracts': patch +--- + +add legacy fallback to RMN + + +PR issue: CCIP-4261 + +Solidity Review issue: CCIP-3966 \ No newline at end of file diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index 64cc09bc15d..cfa764656a4 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -573,27 +573,27 @@ RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_Dupli RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_NotEnoughObservers_reverts() (gas: 21405) RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_OutOfBoundsNodesLength_reverts() (gas: 137318) RMNHome_validateStaticAndDynamicConfig:test_validateStaticAndDynamicConfig_OutOfBoundsObserverNodeIndex_reverts() (gas: 20522) -RMNRemote_constructor:test_constructor_success() (gas: 8334) -RMNRemote_constructor:test_constructor_zeroChainSelector_reverts() (gas: 59184) -RMNRemote_curse:test_curse_AlreadyCursed_duplicateSubject_reverts() (gas: 154479) -RMNRemote_curse:test_curse_calledByNonOwner_reverts() (gas: 18712) -RMNRemote_curse:test_curse_success() (gas: 149431) -RMNRemote_global_and_legacy_curses:test_global_and_legacy_curses_success() (gas: 133512) +RMNRemote_constructor:test_constructor() (gas: 8398) +RMNRemote_curse:test_curse_AlreadyCursed_duplicateSubject_reverts() (gas: 154501) +RMNRemote_curse:test_curse_calledByNonOwner_reverts() (gas: 18734) +RMNRemote_curse:test_curse_success() (gas: 149475) +RMNRemote_global_and_legacy_curses:test_global_and_legacy_curses_success() (gas: 133441) +RMNRemote_isBlessed:test_isBlessed() (gas: 17588) RMNRemote_setConfig:test_setConfig_ZeroValueNotAllowed_revert() (gas: 37971) RMNRemote_setConfig:test_setConfig_addSigner_removeSigner_success() (gas: 993448) RMNRemote_setConfig:test_setConfig_duplicateOnChainPublicKey_reverts() (gas: 323540) RMNRemote_setConfig:test_setConfig_invalidSignerOrder_reverts() (gas: 80201) RMNRemote_setConfig:test_setConfig_notEnoughSigners_reverts() (gas: 54232) -RMNRemote_uncurse:test_uncurse_NotCursed_duplicatedUncurseSubject_reverts() (gas: 51993) -RMNRemote_uncurse:test_uncurse_calledByNonOwner_reverts() (gas: 18682) -RMNRemote_uncurse:test_uncurse_success() (gas: 40171) -RMNRemote_verify_withConfigNotSet:test_verify_reverts() (gas: 13578) -RMNRemote_verify_withConfigSet:test_verify_InvalidSignature_reverts() (gas: 96449) -RMNRemote_verify_withConfigSet:test_verify_OutOfOrderSignatures_duplicateSignature_reverts() (gas: 94267) -RMNRemote_verify_withConfigSet:test_verify_OutOfOrderSignatures_not_sorted_reverts() (gas: 101330) -RMNRemote_verify_withConfigSet:test_verify_ThresholdNotMet_reverts() (gas: 304634) -RMNRemote_verify_withConfigSet:test_verify_UnexpectedSigner_reverts() (gas: 428126) -RMNRemote_verify_withConfigSet:test_verify_success() (gas: 86159) +RMNRemote_uncurse:test_uncurse_NotCursed_duplicatedUncurseSubject_reverts() (gas: 51940) +RMNRemote_uncurse:test_uncurse_calledByNonOwner_reverts() (gas: 18615) +RMNRemote_uncurse:test_uncurse_success() (gas: 40135) +RMNRemote_verify_withConfigNotSet:test_verify_reverts() (gas: 13600) +RMNRemote_verify_withConfigSet:test_verify_InvalidSignature_reverts() (gas: 96471) +RMNRemote_verify_withConfigSet:test_verify_OutOfOrderSignatures_duplicateSignature_reverts() (gas: 94289) +RMNRemote_verify_withConfigSet:test_verify_OutOfOrderSignatures_not_sorted_reverts() (gas: 101352) +RMNRemote_verify_withConfigSet:test_verify_ThresholdNotMet_reverts() (gas: 304744) +RMNRemote_verify_withConfigSet:test_verify_UnexpectedSigner_reverts() (gas: 428284) +RMNRemote_verify_withConfigSet:test_verify_success() (gas: 86181) RateLimiter_constructor:test_Constructor_Success() (gas: 19806) RateLimiter_consume:test_AggregateValueMaxCapacityExceeded_Revert() (gas: 16042) RateLimiter_consume:test_AggregateValueRateLimitReached_Revert() (gas: 22435) diff --git a/contracts/src/v0.8/ccip/rmn/RMNRemote.sol b/contracts/src/v0.8/ccip/rmn/RMNRemote.sol index 5faa1d720e7..4e7ce766443 100644 --- a/contracts/src/v0.8/ccip/rmn/RMNRemote.sol +++ b/contracts/src/v0.8/ccip/rmn/RMNRemote.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.24; import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {IRMN} from "../interfaces/IRMN.sol"; import {IRMNRemote} from "../interfaces/IRMNRemote.sol"; import {Ownable2StepMsgSender} from "../../shared/access/Ownable2StepMsgSender.sol"; @@ -19,7 +20,10 @@ bytes16 constant LEGACY_CURSE_SUBJECT = 0x01000000000000000000000000000000; bytes16 constant GLOBAL_CURSE_SUBJECT = 0x01000000000000000000000000000001; /// @notice This contract supports verification of RMN reports for any Any2EVM OffRamp. -contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote { +/// @dev This contract implements both the new IRMNRemote interface and the legacy IRMN interface. This is to allow for +/// a seamless migration from the legacy RMN contract to this one. The only function that has been dropped in the newer +/// interface is `isBlessed`. For the `isBlessed` function, this contract relays the call to the legacy RMN contract. +contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote, IRMN { using EnumerableSet for EnumerableSet.Bytes16Set; error AlreadyCursed(bytes16 subject); @@ -33,6 +37,7 @@ contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote { error ThresholdNotMet(); error UnexpectedSigner(); error ZeroValueNotAllowed(); + error IsBlessedNotAvailable(); event ConfigSet(uint32 indexed version, Config config); event Cursed(bytes16[] subjects); @@ -67,6 +72,7 @@ contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote { string public constant override typeAndVersion = "RMNRemote 1.6.0-dev"; uint64 internal immutable i_localChainSelector; + IRMN internal immutable i_legacyRMN; Config private s_config; uint32 private s_configCount; @@ -80,11 +86,11 @@ contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote { mapping(address signer => bool exists) private s_signers; // for more gas efficient verify. /// @param localChainSelector the chain selector of the chain this contract is deployed to. - constructor( - uint64 localChainSelector - ) { + constructor(uint64 localChainSelector, IRMN legacyRMN) { if (localChainSelector == 0) revert ZeroValueNotAllowed(); i_localChainSelector = localChainSelector; + + i_legacyRMN = legacyRMN; } // ================================================================ @@ -248,7 +254,7 @@ contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote { } /// @inheritdoc IRMNRemote - function isCursed() external view returns (bool) { + function isCursed() external view override(IRMN, IRMNRemote) returns (bool) { // There are zero curses under normal circumstances, which means it's cheaper to check for the absence of curses. // than to check the subject list twice, as we have to check for both the legacy and global curse subjects. if (s_cursedSubjects.length() == 0) { @@ -260,7 +266,7 @@ contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote { /// @inheritdoc IRMNRemote function isCursed( bytes16 subject - ) external view returns (bool) { + ) external view override(IRMN, IRMNRemote) returns (bool) { // There are zero curses under normal circumstances, which means it's cheaper to check for the absence of curses. // than to check the subject list twice, as we have to check for both the given and global curse subjects. if (s_cursedSubjects.length() == 0) { @@ -268,4 +274,20 @@ contract RMNRemote is Ownable2StepMsgSender, ITypeAndVersion, IRMNRemote { } return s_cursedSubjects.contains(subject) || s_cursedSubjects.contains(GLOBAL_CURSE_SUBJECT); } + + // ================================================================ + // │ Legacy pass through │ + // ================================================================ + + /// @inheritdoc IRMN + /// @dev This function is only expected to be used for messages from CCIP versions below 1.6. + function isBlessed( + TaggedRoot calldata taggedRoot + ) external view returns (bool) { + if (i_legacyRMN == IRMN(address(0))) { + revert IsBlessedNotAvailable(); + } + + return i_legacyRMN.isBlessed(taggedRoot); + } } diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.constructor.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.constructor.t.sol index 1cc9d9addb7..413ef4a6797 100644 --- a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.constructor.t.sol +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.constructor.t.sol @@ -1,16 +1,10 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {RMNRemote} from "../../../rmn/RMNRemote.sol"; import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; contract RMNRemote_constructor is RMNRemoteSetup { - function test_constructor_success() public view { + function test_constructor() public view { assertEq(s_rmnRemote.getLocalChainSelector(), 1); } - - function test_constructor_zeroChainSelector_reverts() public { - vm.expectRevert(RMNRemote.ZeroValueNotAllowed.selector); - new RMNRemote(0); - } } diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.isBlessed.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.isBlessed.t.sol new file mode 100644 index 00000000000..aabfe74bf4d --- /dev/null +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemote.isBlessed.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRMN} from "../../../interfaces/IRMN.sol"; + +import {RMNRemote} from "../../../rmn/RMNRemote.sol"; +import {RMNRemoteSetup} from "./RMNRemoteSetup.t.sol"; + +contract RMNRemote_isBlessed is RMNRemoteSetup { + function test_isBlessed() public { + IRMN.TaggedRoot memory taggedRoot = IRMN.TaggedRoot({root: keccak256("root"), commitStore: makeAddr("commitStore")}); + + vm.mockCall( + address(s_legacyRMN), abi.encodeWithSelector(s_legacyRMN.isBlessed.selector, taggedRoot), abi.encode(true) + ); + + assertTrue(s_rmnRemote.isBlessed(taggedRoot)); + + vm.mockCall( + address(s_legacyRMN), abi.encodeWithSelector(s_legacyRMN.isBlessed.selector, taggedRoot), abi.encode(false) + ); + + assertFalse(s_rmnRemote.isBlessed(taggedRoot)); + } + + function test_isBlessed_RevertWhen_IsBlessedNotAvailable() public { + IRMN.TaggedRoot memory taggedRoot = IRMN.TaggedRoot({root: keccak256("root"), commitStore: makeAddr("commitStore")}); + + s_rmnRemote = new RMNRemote(100, IRMN(address(0))); + + vm.expectRevert(RMNRemote.IsBlessedNotAvailable.selector); + s_rmnRemote.isBlessed(taggedRoot); + } +} diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemoteSetup.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemoteSetup.t.sol index b32dcd98a1a..afb65eeab11 100644 --- a/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemoteSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote/RMNRemoteSetup.t.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; +import {IRMN} from "../../../interfaces/IRMN.sol"; import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; + import {Internal} from "../../../libraries/Internal.sol"; import {RMNRemote} from "../../../rmn/RMNRemote.sol"; import {BaseTest} from "../../BaseTest.t.sol"; @@ -21,9 +23,11 @@ contract RMNRemoteSetup is BaseTest { bytes16 internal constant CURSE_SUBJ_2 = bytes16(keccak256("subject 2")); bytes16[] internal s_curseSubjects; + IRMN internal s_legacyRMN = IRMN(makeAddr("legacyRMN")); + function setUp() public virtual override { super.setUp(); - s_rmnRemote = new RMNRemote(1); + s_rmnRemote = new RMNRemote(1, s_legacyRMN); OFF_RAMP_ADDRESS = makeAddr("OFF RAMP"); s_curseSubjects = [CURSE_SUBJ_1, CURSE_SUBJ_2]; diff --git a/core/capabilities/ccip/ccipevm/encodingUtilsAbi.json b/core/capabilities/ccip/ccipevm/encodingUtilsAbi.json new file mode 100644 index 00000000000..4ebb363bffe --- /dev/null +++ b/core/capabilities/ccip/ccipevm/encodingUtilsAbi.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DoNotDeploy","type":"error"},{"inputs":[{"internalType":"bytes32","name":"rmnReportVersion","type":"bytes32"},{"components":[{"internalType":"uint256","name":"destChainId","type":"uint256"},{"internalType":"uint64","name":"destChainSelector","type":"uint64"},{"internalType":"address","name":"rmnRemoteContractAddress","type":"address"},{"internalType":"address","name":"offrampAddress","type":"address"},{"internalType":"bytes32","name":"rmnHomeContractConfigDigest","type":"bytes32"},{"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":"struct Internal.MerkleRoot[]","name":"destLaneUpdates","type":"tuple[]"}],"internalType":"struct RMNRemote.Report","name":"rmnReport","type":"tuple"}],"name":"_rmnReport","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/core/capabilities/ccip/ccipevm/rmncrypto.go b/core/capabilities/ccip/ccipevm/rmncrypto.go index 1b97f5cc3bb..37b909dabe2 100644 --- a/core/capabilities/ccip/ccipevm/rmncrypto.go +++ b/core/capabilities/ccip/ccipevm/rmncrypto.go @@ -3,6 +3,7 @@ package ccipevm import ( "bytes" "context" + _ "embed" "errors" "fmt" "math/big" @@ -18,8 +19,10 @@ import ( // encodingUtilsAbi is the ABI for the EncodingUtils contract. // Should be imported when gethwrappers are moved from ccip repo to core. -// nolint:lll -const encodingUtilsAbiRaw = `[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"DoNotDeploy","type":"error"},{"inputs":[{"internalType":"bytes32","name":"rmnReportVersion","type":"bytes32"},{"components":[{"internalType":"uint256","name":"destChainId","type":"uint256"},{"internalType":"uint64","name":"destChainSelector","type":"uint64"},{"internalType":"address","name":"rmnRemoteContractAddress","type":"address"},{"internalType":"address","name":"offrampAddress","type":"address"},{"internalType":"bytes32","name":"rmnHomeContractConfigDigest","type":"bytes32"},{"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":"struct Internal.MerkleRoot[]","name":"destLaneUpdates","type":"tuple[]"}],"internalType":"struct RMNRemote.Report","name":"rmnReport","type":"tuple"}],"name":"_rmnReport","outputs":[],"stateMutability":"nonpayable","type":"function"}]` +// +//go:embed encodingUtilsAbi.json +var encodingUtilsAbiRaw string + const addressEncodeAbiRaw = `[{"name":"method","type":"function","inputs":[{"name": "", "type": "address"}]}]` var ( diff --git a/core/capabilities/integration_tests/keystone/setup.go b/core/capabilities/integration_tests/keystone/setup.go index b9b98baaf7e..f4b29323537 100644 --- a/core/capabilities/integration_tests/keystone/setup.go +++ b/core/capabilities/integration_tests/keystone/setup.go @@ -130,7 +130,7 @@ func newReport(t *testing.T, feedID [32]byte, price *big.Int, timestamp int64) [ v3Codec := reportcodec.NewReportCodec(feedID, logger.TestLogger(t)) raw, err := v3Codec.BuildReport(ctx, v3.ReportFields{ BenchmarkPrice: price, - //nolint:gosec // disable G115 + Timestamp: uint32(timestamp), Bid: big.NewInt(0), Ask: big.NewInt(0), diff --git a/core/capabilities/launcher.go b/core/capabilities/launcher.go index 97aea5d3c8c..27c43fe0a53 100644 --- a/core/capabilities/launcher.go +++ b/core/capabilities/launcher.go @@ -135,6 +135,7 @@ func (w *launcher) Name() string { } func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegistry) error { + w.lggr.Debug("CapabilitiesLauncher triggered...") w.registry.SetLocalRegistry(state) allDONIDs := []registrysyncer.DonID{} @@ -222,6 +223,7 @@ func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegist return errors.New("invariant violation: node is part of more than one workflowDON") } + w.lggr.Debug("Notifying DON set...") w.workflowDonNotifier.NotifyDonSet(myDON.DON) for _, rcd := range remoteCapabilityDONs { diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index c44cebe0840..a67670df318 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "math" "math/big" "strings" "testing" @@ -357,9 +358,12 @@ func (c *SimulatedBackendClient) SubscribeToHeads( case h := <-ch: var head *evmtypes.Head if h != nil { + if h.Time > math.MaxInt64 { + c.t.Fatalf("time overflows int64: %d", h.Time) + } head = &evmtypes.Head{ Difficulty: h.Difficulty, - Timestamp: time.Unix(int64(h.Time), 0), //nolint:gosec + Timestamp: time.Unix(int64(h.Time), 0), //nolint:gosec // G115 false positive Number: h.Number.Int64(), Hash: h.Hash(), ParentHash: h.ParentHash, diff --git a/core/chains/evm/config/toml/defaults/zkSync_Mainnet.toml b/core/chains/evm/config/toml/defaults/zkSync_Mainnet.toml index 85282ea81b3..c4e0cef4c61 100644 --- a/core/chains/evm/config/toml/defaults/zkSync_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/zkSync_Mainnet.toml @@ -16,3 +16,6 @@ OracleType = 'zksync' [HeadTracker] HistoryDepth = 50 + +[OCR2.Automation] +GasLimit = 6_000_000 diff --git a/core/chains/evm/config/toml/defaults/zkSync_Sepolia.toml b/core/chains/evm/config/toml/defaults/zkSync_Sepolia.toml index 78ed3c0768d..ed7c37b8ca1 100644 --- a/core/chains/evm/config/toml/defaults/zkSync_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/zkSync_Sepolia.toml @@ -16,3 +16,6 @@ OracleType = 'zksync' [HeadTracker] HistoryDepth = 50 + +[OCR2.Automation] +GasLimit = 6_000_000 diff --git a/core/chains/evm/label/label.go b/core/chains/evm/label/label.go index 4cca89f93fb..b6e80acfadf 100644 --- a/core/chains/evm/label/label.go +++ b/core/chains/evm/label/label.go @@ -1,6 +1,5 @@ package label -// nolint const ( MaxInFlightTransactionsWarning = `WARNING: If this happens a lot, you may need to increase EVM.Transactions.MaxInFlight to boost your node's transaction throughput, however you do this at your own risk. You MUST first ensure your ethereum node is configured not to ever evict local transactions that exceed this number otherwise the node can get permanently stuck. See the performance guide for more details: https://docs.chain.link/docs/evm-performance-configuration/` MaxQueuedTransactionsWarning = `WARNING: Hitting EVM.Transactions.MaxQueued is a sanity limit and should never happen under normal operation. Unless you are operating with very high throughput, this error is unlikely to be a problem with your Chainlink node configuration, and instead more likely to be caused by a problem with your eth node's connectivity. Check your eth node: it may not be broadcasting transactions to the network, or it might be overloaded and evicting Chainlink's transactions from its mempool. It is recommended to run Chainlink with multiple primary and sendonly nodes for redundancy and to ensure fast and reliable transaction propagation. Increasing EVM.Transactions.MaxQueued will allow Chainlink to buffer more unsent transactions, but you should only do this if you need very high burst transmission rates. If you don't need very high burst throughput, increasing this limit is not the correct action to take here and will probably make things worse. See the performance guide for more details: https://docs.chain.link/docs/evm-performance-configuration/` diff --git a/core/chains/evm/logpoller/log_poller.go b/core/chains/evm/logpoller/log_poller.go index 3848c44da82..6ef4fefecee 100644 --- a/core/chains/evm/logpoller/log_poller.go +++ b/core/chains/evm/logpoller/log_poller.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" "math/big" - "math/rand" + "math/rand/v2" "sort" "strings" "sync" @@ -687,7 +687,7 @@ func (lp *logPoller) backgroundWorkerRun() { // Start initial prune of unmatched logs after 5-15 successful expired log prunes, so that not all chains start // around the same time. After that, every 20 successful expired log prunes. - successfulExpiredLogPrunes := 5 + rand.Intn(10) //nolint:gosec + successfulExpiredLogPrunes := 5 + rand.IntN(10) //nolint:gosec // G404 for { select { diff --git a/core/chains/evm/txmgr/client.go b/core/chains/evm/txmgr/client.go index 9b2bcab6ebc..9ec175048d3 100644 --- a/core/chains/evm/txmgr/client.go +++ b/core/chains/evm/txmgr/client.go @@ -121,7 +121,7 @@ func (c *evmTxmClient) SequenceAt(ctx context.Context, addr common.Address, bloc if nonce > math.MaxInt64 { return 0, fmt.Errorf("overflow for nonce: %d", nonce) } - //nolint:gosec // disable G115 + return evmtypes.Nonce(nonce), err } diff --git a/core/chains/evm/txmgr/evm_tx_store.go b/core/chains/evm/txmgr/evm_tx_store.go index d76580907b3..95756790cf3 100644 --- a/core/chains/evm/txmgr/evm_tx_store.go +++ b/core/chains/evm/txmgr/evm_tx_store.go @@ -1411,9 +1411,6 @@ func (o *evmTxStore) UpdateTxFatalErrorAndDeleteAttempts(ctx context.Context, et var cancel context.CancelFunc ctx, cancel = o.stopCh.Ctx(ctx) defer cancel() - if etx.State != txmgr.TxInProgress && etx.State != txmgr.TxUnstarted && etx.State != txmgr.TxConfirmed { - return pkgerrors.Errorf("can only transition to fatal_error from in_progress, unstarted, or confirmed, transaction is currently %s", etx.State) - } if !etx.Error.Valid { return errors.New("expected error field to be set") } diff --git a/core/chains/evm/txmgr/finalizer.go b/core/chains/evm/txmgr/finalizer.go index b5fe5ae37e2..bc496202cd6 100644 --- a/core/chains/evm/txmgr/finalizer.go +++ b/core/chains/evm/txmgr/finalizer.go @@ -580,7 +580,7 @@ func (f *evmFinalizer) ProcessOldTxsWithoutReceipts(ctx context.Context, oldTxID "an external wallet has been used to send a transaction from account %s with nonce %s."+ " Please note that Chainlink requires exclusive ownership of it's private keys and sharing keys across multiple"+ " chainlink instances, or using the chainlink keys with an external wallet is NOT SUPPORTED and WILL lead to missed transactions", - oldTx.ID, head.BlockNumber(), latestFinalizedHead.BlockNumber(), oldTx.FromAddress, oldTx.Sequence.String()), "txID", oldTx.ID, "sequence", oldTx.Sequence.String(), "fromAddress", oldTx.FromAddress) + oldTx.ID, head.BlockNumber(), latestFinalizedHead.BlockNumber(), oldTx.FromAddress, oldTx.Sequence), "txID", oldTx.ID, "sequence", oldTx.Sequence, "fromAddress", oldTx.FromAddress) // Signal pending tasks for these transactions as failed // Store errors and continue to allow all transactions a chance to be signaled diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index bead4ba5afd..412231308b6 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -469,14 +469,12 @@ func (s *Shell) runNode(c *cli.Context) error { } } - if s.Config.Capabilities().WorkflowRegistry().Address() != "" { - err2 := app.GetKeyStore().Workflow().EnsureKey(rootCtx) - if err2 != nil { - return errors.Wrap(err2, "failed to ensure workflow key") - } + err2 := app.GetKeyStore().Workflow().EnsureKey(rootCtx) + if err2 != nil { + return errors.Wrap(err2, "failed to ensure workflow key") } - err2 := app.GetKeyStore().CSA().EnsureKey(rootCtx) + err2 = app.GetKeyStore().CSA().EnsureKey(rootCtx) if err2 != nil { return errors.Wrap(err2, "failed to ensure CSA key") } @@ -686,7 +684,6 @@ func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { nonces[i] = evmtypes.Nonce(beginningNonce + i) } if gasPriceWei <= math.MaxInt64 { - //nolint:gosec // disable G115 return s.errorOut(ec.ForceRebroadcast(ctx, nonces, gas.EvmFee{GasPrice: assets.NewWeiI(int64(gasPriceWei))}, address, uint64(overrideGasLimit))) } return s.errorOut(fmt.Errorf("integer overflow conversion error. GasPrice: %v", gasPriceWei)) diff --git a/core/config/app_config.go b/core/config/app_config.go index 4cb7f1f610c..3f2a5472b24 100644 --- a/core/config/app_config.go +++ b/core/config/app_config.go @@ -8,7 +8,6 @@ import ( "go.uber.org/zap/zapcore" ) -// nolint var ( ErrEnvUnset = pkgerrors.New("env var unset") ) diff --git a/core/gethwrappers/ccip/generated/rmn_remote/rmn_remote.go b/core/gethwrappers/ccip/generated/rmn_remote/rmn_remote.go index dd7655b92a1..2c7c367ab1f 100644 --- a/core/gethwrappers/ccip/generated/rmn_remote/rmn_remote.go +++ b/core/gethwrappers/ccip/generated/rmn_remote/rmn_remote.go @@ -35,6 +35,11 @@ type IRMNRemoteSignature struct { S [32]byte } +type IRMNTaggedRoot struct { + CommitStore common.Address + Root [32]byte +} + type InternalMerkleRoot struct { SourceChainSelector uint64 OnRampAddress []byte @@ -55,15 +60,15 @@ type RMNRemoteSigner struct { } var RMNRemoteMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"localChainSelector\",\"type\":\"uint64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"AlreadyCursed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotTransferToSelf\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ConfigNotSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DuplicateOnchainPublicKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSignerOrder\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MustBeProposedOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"NotCursed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotEnoughSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OutOfOrderSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OwnerCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ThresholdNotMet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnexpectedSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroValueNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"rmnHomeContractConfigDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"onchainPublicKey\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"nodeIndex\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Signer[]\",\"name\":\"signers\",\"type\":\"tuple[]\"},{\"internalType\":\"uint64\",\"name\":\"f\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structRMNRemote.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"name\":\"Cursed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"name\":\"Uncursed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"curse\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"name\":\"curse\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCursedSubjects\",\"outputs\":[{\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLocalChainSelector\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"localChainSelector\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getReportDigestHeader\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"digestHeader\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVersionedConfig\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"rmnHomeContractConfigDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"onchainPublicKey\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"nodeIndex\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Signer[]\",\"name\":\"signers\",\"type\":\"tuple[]\"},{\"internalType\":\"uint64\",\"name\":\"f\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"isCursed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isCursed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"rmnHomeContractConfigDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"onchainPublicKey\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"nodeIndex\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Signer[]\",\"name\":\"signers\",\"type\":\"tuple[]\"},{\"internalType\":\"uint64\",\"name\":\"f\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Config\",\"name\":\"newConfig\",\"type\":\"tuple\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"uncurse\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"name\":\"uncurse\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"offrampAddress\",\"type\":\"address\"},{\"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\":\"signatures\",\"type\":\"tuple[]\"}],\"name\":\"verify\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x60a06040523480156200001157600080fd5b50604051620020ff380380620020ff833981016040819052620000349162000142565b336000816200005657604051639b15e16f60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b038481169190911790915581161562000089576200008981620000c8565b5050806001600160401b0316600003620000b65760405163273e150360e21b815260040160405180910390fd5b6001600160401b031660805262000174565b336001600160a01b03821603620000f257604051636d6c4ee560e11b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b03838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000602082840312156200015557600080fd5b81516001600160401b03811681146200016d57600080fd5b9392505050565b608051611f68620001976000396000818161027a0152610a2c0152611f686000f3fe608060405234801561001057600080fd5b50600436106101005760003560e01c806370a9089e11610097578063d881e09211610066578063d881e09214610257578063eaa83ddd1461026c578063f2fde38b146102a4578063f8bb876e146102b757600080fd5b806370a9089e1461020157806379ba5097146102145780638da5cb5b1461021c5780639a19b3291461024457600080fd5b8063397796f7116100d3578063397796f7146101a557806362eed415146101ad5780636509a954146101c05780636d2d3993146101ee57600080fd5b8063181f5a7714610105578063198f0f77146101575780631add205f1461016c5780632cbc26bb14610182575b600080fd5b6101416040518060400160405280601381526020017f524d4e52656d6f746520312e362e302d6465760000000000000000000000000081525081565b60405161014e9190611389565b60405180910390f35b61016a61016536600461139c565b6102ca565b005b6101746106c4565b60405161014e9291906113d7565b6101956101903660046114b5565b6107bc565b604051901515815260200161014e565b610195610819565b61016a6101bb3660046114b5565b610893565b6040517f9651943783dbf81935a60e98f218a9d9b5b28823fb2228bbd91320d632facf53815260200161014e565b61016a6101fc3660046114b5565b610907565b61016a61020f36600461153e565b610977565b61016a610cd2565b60015460405173ffffffffffffffffffffffffffffffffffffffff909116815260200161014e565b61016a6102523660046116bd565b610da0565b61025f610ea6565b60405161014e919061175a565b60405167ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016815260200161014e565b61016a6102b23660046117c0565b610eb2565b61016a6102c53660046116bd565b610ec6565b6102d2610fb8565b803561030a576040517f9cf8540c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60015b61031a60208301836117dd565b90508110156103ea5761033060208301836117dd565b8281811061034057610340611845565b90506040020160200160208101906103589190611895565b67ffffffffffffffff1661036f60208401846117dd565b61037a6001856118e1565b81811061038957610389611845565b90506040020160200160208101906103a19190611895565b67ffffffffffffffff16106103e2576040517f4485151700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60010161030d565b506103fb6060820160408301611895565b6104069060026118f4565b610411906001611920565b67ffffffffffffffff1661042860208301836117dd565b90501015610462576040517f014c502000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003545b80156104f45760086000600361047d6001856118e1565b8154811061048d5761048d611845565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690556104ed81611941565b9050610466565b5060005b61050560208301836117dd565b905081101561063a576008600061051f60208501856117dd565b8481811061052f5761052f611845565b61054592602060409092020190810191506117c0565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040016000205460ff16156105a6576040517f28cae27d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600860006105b960208601866117dd565b858181106105c9576105c9611845565b6105df92602060409092020190810191506117c0565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556001016104f8565b508060026106488282611a2f565b5050600580546000919082906106639063ffffffff16611b6a565b91906101000a81548163ffffffff021916908363ffffffff160217905590508063ffffffff167f7f22bf988149dbe8de8fb879c6b97a4e56e68b2bd57421ce1a4e79d4ef6b496c836040516106b89190611b8d565b60405180910390a25050565b6040805160608082018352600080835260208301919091529181018290526005546040805160608101825260028054825260038054845160208281028201810190965281815263ffffffff9096169592948593818601939092909160009084015b82821015610793576000848152602090819020604080518082019091529084015473ffffffffffffffffffffffffffffffffffffffff8116825274010000000000000000000000000000000000000000900467ffffffffffffffff1681830152825260019092019101610725565b505050908252506002919091015467ffffffffffffffff16602090910152919491935090915050565b60006107c8600661100b565b6000036107d757506000919050565b6107e2600683611015565b80610813575061081360067f0100000000000000000000000000000100000000000000000000000000000000611015565b92915050565b6000610825600661100b565b6000036108325750600090565b61085d60067f0100000000000000000000000000000000000000000000000000000000000000611015565b8061088e575061088e60067f0100000000000000000000000000000100000000000000000000000000000000611015565b905090565b6040805160018082528183019092526000916020808301908036833701905050905081816000815181106108c9576108c9611845565b7fffffffffffffffffffffffffffffffff000000000000000000000000000000009092166020928302919091019091015261090381610ec6565b5050565b60408051600180825281830190925260009160208083019080368337019050509050818160008151811061093d5761093d611845565b7fffffffffffffffffffffffffffffffff000000000000000000000000000000009092166020928302919091019091015261090381610da0565b60055463ffffffff166000036109b9576040517face124bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6004546109d19067ffffffffffffffff166001611920565b67ffffffffffffffff16811015610a14576040517f59fa4a9300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160c08101825246815267ffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166020820152309181019190915273ffffffffffffffffffffffffffffffffffffffff8616606082015260025460808201526000907f9651943783dbf81935a60e98f218a9d9b5b28823fb2228bbd91320d632facf539060a08101610ab08789611c97565b9052604051610ac3929190602001611df7565b60405160208183030381529060405280519060200120905060008060005b84811015610cc757600184601b888885818110610b0057610b00611845565b90506040020160000135898986818110610b1c57610b1c611845565b9050604002016020013560405160008152602001604052604051610b5c949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015610b7e573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015192505073ffffffffffffffffffffffffffffffffffffffff8216610bf6576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1610610c5b576040517fbbe15e7f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526008602052604090205460ff16610cba576040517faaaa914100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9091508190600101610ae1565b505050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610d23576040517f02b543c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000008082163390811790935560008054909116815560405173ffffffffffffffffffffffffffffffffffffffff909216929183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610da8610fb8565b60005b8151811015610e6b57610de1828281518110610dc957610dc9611845565b6020026020010151600661105390919063ffffffff16565b610e6357818181518110610df757610df7611845565b60200260200101516040517f73281fa1000000000000000000000000000000000000000000000000000000008152600401610e5a91907fffffffffffffffffffffffffffffffff0000000000000000000000000000000091909116815260200190565b60405180910390fd5b600101610dab565b507f0676e709c9cc74fa0519fd78f7c33be0f1b2b0bae0507c724aef7229379c6ba181604051610e9b919061175a565b60405180910390a150565b606061088e6006611081565b610eba610fb8565b610ec38161108e565b50565b610ece610fb8565b60005b8151811015610f8857610f07828281518110610eef57610eef611845565b6020026020010151600661115290919063ffffffff16565b610f8057818181518110610f1d57610f1d611845565b60200260200101516040517f19d5c79b000000000000000000000000000000000000000000000000000000008152600401610e5a91907fffffffffffffffffffffffffffffffff0000000000000000000000000000000091909116815260200190565b600101610ed1565b507f1716e663a90a76d3b6c7e5f680673d1b051454c19c627e184c8daf28f3104f7481604051610e9b919061175a565b60015473ffffffffffffffffffffffffffffffffffffffff163314611009576040517f2b5c74de00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6000610813825490565b7fffffffffffffffffffffffffffffffff000000000000000000000000000000008116600090815260018301602052604081205415155b9392505050565b600061104c837fffffffffffffffffffffffffffffffff000000000000000000000000000000008416611180565b6060600061104c8361127a565b3373ffffffffffffffffffffffffffffffffffffffff8216036110dd576040517fdad89dca00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600061104c837fffffffffffffffffffffffffffffffff0000000000000000000000000000000084166112d6565b600081815260018301602052604081205480156112695760006111a46001836118e1565b85549091506000906111b8906001906118e1565b905080821461121d5760008660000182815481106111d8576111d8611845565b90600052602060002001549050808760000184815481106111fb576111fb611845565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061122e5761122e611f2c565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610813565b6000915050610813565b5092915050565b6060816000018054806020026020016040519081016040528092919081815260200182805480156112ca57602002820191906000526020600020905b8154815260200190600101908083116112b6575b50505050509050919050565b600081815260018301602052604081205461131d57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610813565b506000610813565b6000815180845260005b8181101561134b5760208185018101518683018201520161132f565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60208152600061104c6020830184611325565b6000602082840312156113ae57600080fd5b813567ffffffffffffffff8111156113c557600080fd5b82016060818503121561104c57600080fd5b63ffffffff831681526040602080830182905283518383015283810151606080850152805160a085018190526000939291820190849060c08701905b8083101561145c578351805173ffffffffffffffffffffffffffffffffffffffff16835285015167ffffffffffffffff1685830152928401926001929092019190850190611413565b50604088015167ffffffffffffffff81166080890152945098975050505050505050565b80357fffffffffffffffffffffffffffffffff00000000000000000000000000000000811681146114b057600080fd5b919050565b6000602082840312156114c757600080fd5b61104c82611480565b73ffffffffffffffffffffffffffffffffffffffff81168114610ec357600080fd5b60008083601f84011261150457600080fd5b50813567ffffffffffffffff81111561151c57600080fd5b6020830191508360208260061b850101111561153757600080fd5b9250929050565b60008060008060006060868803121561155657600080fd5b8535611561816114d0565b9450602086013567ffffffffffffffff8082111561157e57600080fd5b818801915088601f83011261159257600080fd5b8135818111156115a157600080fd5b8960208260051b85010111156115b657600080fd5b6020830196508095505060408801359150808211156115d457600080fd5b506115e1888289016114f2565b969995985093965092949392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405160a0810167ffffffffffffffff81118282101715611644576116446115f2565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715611691576116916115f2565b604052919050565b600067ffffffffffffffff8211156116b3576116b36115f2565b5060051b60200190565b600060208083850312156116d057600080fd5b823567ffffffffffffffff8111156116e757600080fd5b8301601f810185136116f857600080fd5b803561170b61170682611699565b61164a565b81815260059190911b8201830190838101908783111561172a57600080fd5b928401925b8284101561174f5761174084611480565b8252928401929084019061172f565b979650505050505050565b6020808252825182820181905260009190848201906040850190845b818110156117b45783517fffffffffffffffffffffffffffffffff000000000000000000000000000000001683529284019291840191600101611776565b50909695505050505050565b6000602082840312156117d257600080fd5b813561104c816114d0565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261181257600080fd5b83018035915067ffffffffffffffff82111561182d57600080fd5b6020019150600681901b360382131561153757600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b67ffffffffffffffff81168114610ec357600080fd5b80356114b081611874565b6000602082840312156118a757600080fd5b813561104c81611874565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610813576108136118b2565b67ffffffffffffffff818116838216028082169190828114611918576119186118b2565b505092915050565b67ffffffffffffffff818116838216019080821115611273576112736118b2565b600081611950576119506118b2565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b6000813561081381611874565b813561198e816114d0565b73ffffffffffffffffffffffffffffffffffffffff811690508154817fffffffffffffffffffffffff0000000000000000000000000000000000000000821617835560208401356119de81611874565b7bffffffffffffffff00000000000000000000000000000000000000008160a01b16837fffffffff000000000000000000000000000000000000000000000000000000008416171784555050505050565b81358155600180820160208401357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1853603018112611a6d57600080fd5b8401803567ffffffffffffffff811115611a8657600080fd5b6020820191508060061b3603821315611a9e57600080fd5b68010000000000000000811115611ab757611ab76115f2565b825481845580821015611aec576000848152602081208381019083015b80821015611ae85782825590870190611ad4565b5050505b50600092835260208320925b81811015611b1c57611b0a8385611983565b92840192604092909201918401611af8565b5050505050610903611b3060408401611976565b6002830167ffffffffffffffff82167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000008254161781555050565b600063ffffffff808316818103611b8357611b836118b2565b6001019392505050565b6000602080835260808301843582850152818501357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1863603018112611bd257600080fd5b8501828101903567ffffffffffffffff80821115611bef57600080fd5b8160061b3603831315611c0157600080fd5b6040606060408901528483865260a089019050849550600094505b83851015611c6c578535611c2f816114d0565b73ffffffffffffffffffffffffffffffffffffffff16815285870135611c5481611874565b83168188015294810194600194909401938101611c1c565b611c7860408b0161188a565b67ffffffffffffffff811660608b015296509998505050505050505050565b6000611ca561170684611699565b80848252602080830192508560051b850136811115611cc357600080fd5b855b81811015611deb57803567ffffffffffffffff80821115611ce65760008081fd5b818901915060a08236031215611cfc5760008081fd5b611d04611621565b8235611d0f81611874565b81528286013582811115611d235760008081fd5b8301601f3681830112611d365760008081fd5b813584811115611d4857611d486115f2565b611d77897fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0848401160161164a565b94508085523689828501011115611d9057600091508182fd5b808984018a8701376000898287010152505050818682015260409150611db782840161188a565b8282015260609150611dca82840161188a565b91810191909152608091820135918101919091528552938201938201611cc5565b50919695505050505050565b60006040848352602060408185015261010084018551604086015281860151606067ffffffffffffffff808316606089015260408901519250608073ffffffffffffffffffffffffffffffffffffffff80851660808b015260608b0151945060a081861660a08c015260808c015160c08c015260a08c0151955060c060e08c015286915085518088526101209750878c019250878160051b8d01019750888701965060005b81811015611f19577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee08d8a030184528751868151168a528a810151848c8c0152611ee8858c0182611325565b828e015189168c8f01528983015189168a8d0152918701519a87019a909a5298509689019692890192600101611e9c565b50969d9c50505050505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c6343000818000a", + ABI: "[{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"localChainSelector\",\"type\":\"uint64\"},{\"internalType\":\"contractIRMN\",\"name\":\"legacyRMN\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"AlreadyCursed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotTransferToSelf\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ConfigNotSet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DuplicateOnchainPublicKey\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSignature\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSignerOrder\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IsBlessedNotAvailable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MustBeProposedOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"NotCursed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotEnoughSigners\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyCallableByOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OutOfOrderSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OwnerCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ThresholdNotMet\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UnexpectedSigner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroValueNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"rmnHomeContractConfigDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"onchainPublicKey\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"nodeIndex\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Signer[]\",\"name\":\"signers\",\"type\":\"tuple[]\"},{\"internalType\":\"uint64\",\"name\":\"f\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structRMNRemote.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"name\":\"ConfigSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"name\":\"Cursed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"name\":\"Uncursed\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"curse\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"name\":\"curse\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCursedSubjects\",\"outputs\":[{\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLocalChainSelector\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"localChainSelector\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getReportDigestHeader\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"digestHeader\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVersionedConfig\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"version\",\"type\":\"uint32\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"rmnHomeContractConfigDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"onchainPublicKey\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"nodeIndex\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Signer[]\",\"name\":\"signers\",\"type\":\"tuple[]\"},{\"internalType\":\"uint64\",\"name\":\"f\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Config\",\"name\":\"config\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"commitStore\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"internalType\":\"structIRMN.TaggedRoot\",\"name\":\"taggedRoot\",\"type\":\"tuple\"}],\"name\":\"isBlessed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"isCursed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isCursed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"rmnHomeContractConfigDigest\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"onchainPublicKey\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"nodeIndex\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Signer[]\",\"name\":\"signers\",\"type\":\"tuple[]\"},{\"internalType\":\"uint64\",\"name\":\"f\",\"type\":\"uint64\"}],\"internalType\":\"structRMNRemote.Config\",\"name\":\"newConfig\",\"type\":\"tuple\"}],\"name\":\"setConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16\",\"name\":\"subject\",\"type\":\"bytes16\"}],\"name\":\"uncurse\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes16[]\",\"name\":\"subjects\",\"type\":\"bytes16[]\"}],\"name\":\"uncurse\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"offrampAddress\",\"type\":\"address\"},{\"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\":\"signatures\",\"type\":\"tuple[]\"}],\"name\":\"verify\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60c06040523480156200001157600080fd5b506040516200230438038062002304833981016040819052620000349162000150565b336000816200005657604051639b15e16f60e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b038481169190911790915581161562000089576200008981620000d6565b5050816001600160401b0316600003620000b65760405163273e150360e21b815260040160405180910390fd5b6001600160401b039091166080526001600160a01b031660a052620001a5565b336001600160a01b038216036200010057604051636d6c4ee560e11b815260040160405180910390fd5b600080546001600160a01b0319166001600160a01b03838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600080604083850312156200016457600080fd5b82516001600160401b03811681146200017c57600080fd5b60208401519092506001600160a01b03811681146200019a57600080fd5b809150509250929050565b60805160a05161212b620001d9600039600081816108c5015261096d0152600081816102a80152610b7c015261212b6000f3fe608060405234801561001057600080fd5b506004361061011b5760003560e01c80636d2d3993116100b25780639a19b32911610081578063eaa83ddd11610066578063eaa83ddd1461029a578063f2fde38b146102d2578063f8bb876e146102e557600080fd5b80639a19b32914610272578063d881e0921461028557600080fd5b80636d2d39931461021c57806370a9089e1461022f57806379ba5097146102425780638da5cb5b1461024a57600080fd5b8063397796f7116100ee578063397796f7146101c05780634d616771146101c857806362eed415146101db5780636509a954146101ee57600080fd5b8063181f5a7714610120578063198f0f77146101725780631add205f146101875780632cbc26bb1461019d575b600080fd5b61015c6040518060400160405280601381526020017f524d4e52656d6f746520312e362e302d6465760000000000000000000000000081525081565b60405161016991906114d9565b60405180910390f35b6101856101803660046114ec565b6102f8565b005b61018f6106f2565b604051610169929190611527565b6101b06101ab366004611605565b6107ea565b6040519015158152602001610169565b6101b0610847565b6101b06101d6366004611620565b6108c1565b6101856101e9366004611605565b6109e3565b6040517f9651943783dbf81935a60e98f218a9d9b5b28823fb2228bbd91320d632facf538152602001610169565b61018561022a366004611605565b610a57565b61018561023d3660046116a6565b610ac7565b610185610e22565b60015460405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610169565b610185610280366004611825565b610ef0565b61028d610ff6565b60405161016991906118c2565b60405167ffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152602001610169565b6101856102e0366004611928565b611002565b6101856102f3366004611825565b611016565b610300611108565b8035610338576040517f9cf8540c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60015b6103486020830183611945565b90508110156104185761035e6020830183611945565b8281811061036e5761036e6119ad565b905060400201602001602081019061038691906119fd565b67ffffffffffffffff1661039d6020840184611945565b6103a8600185611a49565b8181106103b7576103b76119ad565b90506040020160200160208101906103cf91906119fd565b67ffffffffffffffff1610610410576040517f4485151700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60010161033b565b5061042960608201604083016119fd565b610434906002611a5c565b61043f906001611a88565b67ffffffffffffffff166104566020830183611945565b90501015610490576040517f014c502000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003545b8015610522576008600060036104ab600185611a49565b815481106104bb576104bb6119ad565b600091825260208083209091015473ffffffffffffffffffffffffffffffffffffffff168352820192909252604001902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905561051b81611aa9565b9050610494565b5060005b6105336020830183611945565b9050811015610668576008600061054d6020850185611945565b8481811061055d5761055d6119ad565b6105739260206040909202019081019150611928565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040016000205460ff16156105d4576040517f28cae27d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600860006105e76020860186611945565b858181106105f7576105f76119ad565b61060d9260206040909202019081019150611928565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055600101610526565b508060026106768282611b97565b5050600580546000919082906106919063ffffffff16611cd2565b91906101000a81548163ffffffff021916908363ffffffff160217905590508063ffffffff167f7f22bf988149dbe8de8fb879c6b97a4e56e68b2bd57421ce1a4e79d4ef6b496c836040516106e69190611cf5565b60405180910390a25050565b6040805160608082018352600080835260208301919091529181018290526005546040805160608101825260028054825260038054845160208281028201810190965281815263ffffffff9096169592948593818601939092909160009084015b828210156107c1576000848152602090819020604080518082019091529084015473ffffffffffffffffffffffffffffffffffffffff8116825274010000000000000000000000000000000000000000900467ffffffffffffffff1681830152825260019092019101610753565b505050908252506002919091015467ffffffffffffffff16602090910152919491935090915050565b60006107f6600661115b565b60000361080557506000919050565b610810600683611165565b80610841575061084160067f0100000000000000000000000000000100000000000000000000000000000000611165565b92915050565b6000610853600661115b565b6000036108605750600090565b61088b60067f0100000000000000000000000000000000000000000000000000000000000000611165565b806108bc57506108bc60067f0100000000000000000000000000000100000000000000000000000000000000611165565b905090565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16610930576040517f0a7c4edd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f4d61677100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690634d616771906109a2908590600401611dff565b602060405180830381865afa1580156109bf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108419190611e38565b604080516001808252818301909252600091602080830190803683370190505090508181600081518110610a1957610a196119ad565b7fffffffffffffffffffffffffffffffff0000000000000000000000000000000090921660209283029190910190910152610a5381611016565b5050565b604080516001808252818301909252600091602080830190803683370190505090508181600081518110610a8d57610a8d6119ad565b7fffffffffffffffffffffffffffffffff0000000000000000000000000000000090921660209283029190910190910152610a5381610ef0565b60055463ffffffff16600003610b09576040517face124bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600454610b219067ffffffffffffffff166001611a88565b67ffffffffffffffff16811015610b64576040517f59fa4a9300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160c08101825246815267ffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166020820152309181019190915273ffffffffffffffffffffffffffffffffffffffff8616606082015260025460808201526000907f9651943783dbf81935a60e98f218a9d9b5b28823fb2228bbd91320d632facf539060a08101610c008789611e5a565b9052604051610c13929190602001611fba565b60405160208183030381529060405280519060200120905060008060005b84811015610e1757600184601b888885818110610c5057610c506119ad565b90506040020160000135898986818110610c6c57610c6c6119ad565b9050604002016020013560405160008152602001604052604051610cac949392919093845260ff9290921660208401526040830152606082015260800190565b6020604051602081039080840390855afa158015610cce573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015192505073ffffffffffffffffffffffffffffffffffffffff8216610d46576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1610610dab576040517fbbe15e7f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081526008602052604090205460ff16610e0a576040517faaaa914100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9091508190600101610c31565b505050505050505050565b60005473ffffffffffffffffffffffffffffffffffffffff163314610e73576040517f02b543c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000008082163390811790935560008054909116815560405173ffffffffffffffffffffffffffffffffffffffff909216929183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b610ef8611108565b60005b8151811015610fbb57610f31828281518110610f1957610f196119ad565b602002602001015160066111a390919063ffffffff16565b610fb357818181518110610f4757610f476119ad565b60200260200101516040517f73281fa1000000000000000000000000000000000000000000000000000000008152600401610faa91907fffffffffffffffffffffffffffffffff0000000000000000000000000000000091909116815260200190565b60405180910390fd5b600101610efb565b507f0676e709c9cc74fa0519fd78f7c33be0f1b2b0bae0507c724aef7229379c6ba181604051610feb91906118c2565b60405180910390a150565b60606108bc60066111d1565b61100a611108565b611013816111de565b50565b61101e611108565b60005b81518110156110d85761105782828151811061103f5761103f6119ad565b602002602001015160066112a290919063ffffffff16565b6110d05781818151811061106d5761106d6119ad565b60200260200101516040517f19d5c79b000000000000000000000000000000000000000000000000000000008152600401610faa91907fffffffffffffffffffffffffffffffff0000000000000000000000000000000091909116815260200190565b600101611021565b507f1716e663a90a76d3b6c7e5f680673d1b051454c19c627e184c8daf28f3104f7481604051610feb91906118c2565b60015473ffffffffffffffffffffffffffffffffffffffff163314611159576040517f2b5c74de00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6000610841825490565b7fffffffffffffffffffffffffffffffff000000000000000000000000000000008116600090815260018301602052604081205415155b9392505050565b600061119c837fffffffffffffffffffffffffffffffff0000000000000000000000000000000084166112d0565b6060600061119c836113ca565b3373ffffffffffffffffffffffffffffffffffffffff82160361122d576040517fdad89dca00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff838116918217835560015460405192939116917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b600061119c837fffffffffffffffffffffffffffffffff000000000000000000000000000000008416611426565b600081815260018301602052604081205480156113b95760006112f4600183611a49565b855490915060009061130890600190611a49565b905080821461136d576000866000018281548110611328576113286119ad565b906000526020600020015490508087600001848154811061134b5761134b6119ad565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061137e5761137e6120ef565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610841565b6000915050610841565b5092915050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561141a57602002820191906000526020600020905b815481526020019060010190808311611406575b50505050509050919050565b600081815260018301602052604081205461146d57508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610841565b506000610841565b6000815180845260005b8181101561149b5760208185018101518683018201520161147f565b5060006020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b60208152600061119c6020830184611475565b6000602082840312156114fe57600080fd5b813567ffffffffffffffff81111561151557600080fd5b82016060818503121561119c57600080fd5b63ffffffff831681526040602080830182905283518383015283810151606080850152805160a085018190526000939291820190849060c08701905b808310156115ac578351805173ffffffffffffffffffffffffffffffffffffffff16835285015167ffffffffffffffff1685830152928401926001929092019190850190611563565b50604088015167ffffffffffffffff81166080890152945098975050505050505050565b80357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116811461160057600080fd5b919050565b60006020828403121561161757600080fd5b61119c826115d0565b60006040828403121561163257600080fd5b50919050565b73ffffffffffffffffffffffffffffffffffffffff8116811461101357600080fd5b60008083601f84011261166c57600080fd5b50813567ffffffffffffffff81111561168457600080fd5b6020830191508360208260061b850101111561169f57600080fd5b9250929050565b6000806000806000606086880312156116be57600080fd5b85356116c981611638565b9450602086013567ffffffffffffffff808211156116e657600080fd5b818801915088601f8301126116fa57600080fd5b81358181111561170957600080fd5b8960208260051b850101111561171e57600080fd5b60208301965080955050604088013591508082111561173c57600080fd5b506117498882890161165a565b969995985093965092949392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405160a0810167ffffffffffffffff811182821017156117ac576117ac61175a565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156117f9576117f961175a565b604052919050565b600067ffffffffffffffff82111561181b5761181b61175a565b5060051b60200190565b6000602080838503121561183857600080fd5b823567ffffffffffffffff81111561184f57600080fd5b8301601f8101851361186057600080fd5b803561187361186e82611801565b6117b2565b81815260059190911b8201830190838101908783111561189257600080fd5b928401925b828410156118b7576118a8846115d0565b82529284019290840190611897565b979650505050505050565b6020808252825182820181905260009190848201906040850190845b8181101561191c5783517fffffffffffffffffffffffffffffffff0000000000000000000000000000000016835292840192918401916001016118de565b50909695505050505050565b60006020828403121561193a57600080fd5b813561119c81611638565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe184360301811261197a57600080fd5b83018035915067ffffffffffffffff82111561199557600080fd5b6020019150600681901b360382131561169f57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b67ffffffffffffffff8116811461101357600080fd5b8035611600816119dc565b600060208284031215611a0f57600080fd5b813561119c816119dc565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561084157610841611a1a565b67ffffffffffffffff818116838216028082169190828114611a8057611a80611a1a565b505092915050565b67ffffffffffffffff8181168382160190808211156113c3576113c3611a1a565b600081611ab857611ab8611a1a565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b60008135610841816119dc565b8135611af681611638565b73ffffffffffffffffffffffffffffffffffffffff811690508154817fffffffffffffffffffffffff000000000000000000000000000000000000000082161783556020840135611b46816119dc565b7bffffffffffffffff00000000000000000000000000000000000000008160a01b16837fffffffff000000000000000000000000000000000000000000000000000000008416171784555050505050565b81358155600180820160208401357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1853603018112611bd557600080fd5b8401803567ffffffffffffffff811115611bee57600080fd5b6020820191508060061b3603821315611c0657600080fd5b68010000000000000000811115611c1f57611c1f61175a565b825481845580821015611c54576000848152602081208381019083015b80821015611c505782825590870190611c3c565b5050505b50600092835260208320925b81811015611c8457611c728385611aeb565b92840192604092909201918401611c60565b5050505050610a53611c9860408401611ade565b6002830167ffffffffffffffff82167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000008254161781555050565b600063ffffffff808316818103611ceb57611ceb611a1a565b6001019392505050565b6000602080835260808301843582850152818501357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1863603018112611d3a57600080fd5b8501828101903567ffffffffffffffff80821115611d5757600080fd5b8160061b3603831315611d6957600080fd5b6040606060408901528483865260a089019050849550600094505b83851015611dd4578535611d9781611638565b73ffffffffffffffffffffffffffffffffffffffff16815285870135611dbc816119dc565b83168188015294810194600194909401938101611d84565b611de060408b016119f2565b67ffffffffffffffff811660608b015296509998505050505050505050565b604081018235611e0e81611638565b73ffffffffffffffffffffffffffffffffffffffff81168352506020830135602083015292915050565b600060208284031215611e4a57600080fd5b8151801515811461119c57600080fd5b6000611e6861186e84611801565b80848252602080830192508560051b850136811115611e8657600080fd5b855b81811015611fae57803567ffffffffffffffff80821115611ea95760008081fd5b818901915060a08236031215611ebf5760008081fd5b611ec7611789565b8235611ed2816119dc565b81528286013582811115611ee65760008081fd5b8301601f3681830112611ef95760008081fd5b813584811115611f0b57611f0b61175a565b611f3a897fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe084840116016117b2565b94508085523689828501011115611f5357600091508182fd5b808984018a8701376000898287010152505050818682015260409150611f7a8284016119f2565b8282015260609150611f8d8284016119f2565b91810191909152608091820135918101919091528552938201938201611e88565b50919695505050505050565b60006040848352602060408185015261010084018551604086015281860151606067ffffffffffffffff808316606089015260408901519250608073ffffffffffffffffffffffffffffffffffffffff80851660808b015260608b0151945060a081861660a08c015260808c015160c08c015260a08c0151955060c060e08c015286915085518088526101209750878c019250878160051b8d01019750888701965060005b818110156120dc577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffee08d8a030184528751868151168a528a810151848c8c01526120ab858c0182611475565b828e015189168c8f01528983015189168a8d0152918701519a87019a909a529850968901969289019260010161205f565b50969d9c50505050505050505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c6343000818000a", } var RMNRemoteABI = RMNRemoteMetaData.ABI var RMNRemoteBin = RMNRemoteMetaData.Bin -func DeployRMNRemote(auth *bind.TransactOpts, backend bind.ContractBackend, localChainSelector uint64) (common.Address, *types.Transaction, *RMNRemote, error) { +func DeployRMNRemote(auth *bind.TransactOpts, backend bind.ContractBackend, localChainSelector uint64, legacyRMN common.Address) (common.Address, *types.Transaction, *RMNRemote, error) { parsed, err := RMNRemoteMetaData.GetAbi() if err != nil { return common.Address{}, nil, nil, err @@ -72,7 +77,7 @@ func DeployRMNRemote(auth *bind.TransactOpts, backend bind.ContractBackend, loca return common.Address{}, nil, nil, errors.New("GetABI returned nil") } - address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(RMNRemoteBin), backend, localChainSelector) + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(RMNRemoteBin), backend, localChainSelector, legacyRMN) if err != nil { return common.Address{}, nil, nil, err } @@ -291,6 +296,28 @@ func (_RMNRemote *RMNRemoteCallerSession) GetVersionedConfig() (GetVersionedConf return _RMNRemote.Contract.GetVersionedConfig(&_RMNRemote.CallOpts) } +func (_RMNRemote *RMNRemoteCaller) IsBlessed(opts *bind.CallOpts, taggedRoot IRMNTaggedRoot) (bool, error) { + var out []interface{} + err := _RMNRemote.contract.Call(opts, &out, "isBlessed", taggedRoot) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +func (_RMNRemote *RMNRemoteSession) IsBlessed(taggedRoot IRMNTaggedRoot) (bool, error) { + return _RMNRemote.Contract.IsBlessed(&_RMNRemote.CallOpts, taggedRoot) +} + +func (_RMNRemote *RMNRemoteCallerSession) IsBlessed(taggedRoot IRMNTaggedRoot) (bool, error) { + return _RMNRemote.Contract.IsBlessed(&_RMNRemote.CallOpts, taggedRoot) +} + func (_RMNRemote *RMNRemoteCaller) IsCursed(opts *bind.CallOpts, subject [16]byte) (bool, error) { var out []interface{} err := _RMNRemote.contract.Call(opts, &out, "isCursed", subject) @@ -1175,6 +1202,8 @@ type RMNRemoteInterface interface { error) + IsBlessed(opts *bind.CallOpts, taggedRoot IRMNTaggedRoot) (bool, error) + IsCursed(opts *bind.CallOpts, subject [16]byte) (bool, error) IsCursed0(opts *bind.CallOpts) (bool, 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 4274fbd9853..d839ed59f60 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 @@ -23,7 +23,7 @@ registry_module_owner_custom: ../../../contracts/solc/v0.8.24/RegistryModuleOwne report_codec: ../../../contracts/solc/v0.8.24/ReportCodec/ReportCodec.abi ../../../contracts/solc/v0.8.24/ReportCodec/ReportCodec.bin 6c943b39f003aa67c3cefa19a8ff99e846236a058e1ceae77569c3a065ffd5c7 rmn_home: ../../../contracts/solc/v0.8.24/RMNHome/RMNHome.abi ../../../contracts/solc/v0.8.24/RMNHome/RMNHome.bin 84ca84b3d0c00949905a3d10a91255f877cf32b2a0d7f7f7ce3121ced34a8cb7 rmn_proxy_contract: ../../../contracts/solc/v0.8.24/ARMProxy/ARMProxy.abi ../../../contracts/solc/v0.8.24/ARMProxy/ARMProxy.bin b048d8e752e3c41113ebb305c1efa06737ad36b4907b93e627fb0a3113023454 -rmn_remote: ../../../contracts/solc/v0.8.24/RMNRemote/RMNRemote.abi ../../../contracts/solc/v0.8.24/RMNRemote/RMNRemote.bin faee0b0cdbe67f2e28deccf12acd4df13dd90992f6cbc0ba17bab845b8f4eb1c +rmn_remote: ../../../contracts/solc/v0.8.24/RMNRemote/RMNRemote.abi ../../../contracts/solc/v0.8.24/RMNRemote/RMNRemote.bin 941118dfdc6bb042c339cfe8d8e0c7a0b486afb731a785d58a64994e7a13c459 router: ../../../contracts/solc/v0.8.24/Router/Router.abi ../../../contracts/solc/v0.8.24/Router/Router.bin 2e4f0a7826c8abb49d882bb49fc5ff20a186dbd3137624b9097ffed903ae4888 token_admin_registry: ../../../contracts/solc/v0.8.24/TokenAdminRegistry/TokenAdminRegistry.abi ../../../contracts/solc/v0.8.24/TokenAdminRegistry/TokenAdminRegistry.bin 397bc7be08c2848c0f4715f90b16206d6367f78ffb7cd48e2b1dfc0ccc5aea26 token_pool: ../../../contracts/solc/v0.8.24/TokenPool/TokenPool.abi ../../../contracts/solc/v0.8.24/TokenPool/TokenPool.bin da86a1407f31134e7246bde63c80ce8c78ce7d7b44e267f3c1f6030441ff4252 diff --git a/core/logger/null_logger.go b/core/logger/null_logger.go index 9bddd9b336f..e3e841e138c 100644 --- a/core/logger/null_logger.go +++ b/core/logger/null_logger.go @@ -4,7 +4,6 @@ import ( "go.uber.org/zap/zapcore" ) -// nolint var NullLogger Logger = &nullLogger{} type nullLogger struct{} diff --git a/core/scripts/go.mod b/core/scripts/go.mod index a8031243039..9799b070f86 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -302,7 +302,7 @@ require ( github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // 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.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 github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 3b309676c41..74eff9fb62a 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1152,8 +1152,8 @@ github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6An 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.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +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= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749/go.mod h1:nkIegLHodyrrZguxkYEHcNw2vAXv8H8xlCoLzwylcL0= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= diff --git a/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go b/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go index f4e394b7da5..166022ac753 100644 --- a/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go +++ b/core/scripts/keystone/src/05_deploy_initialize_capabilities_registry.go @@ -8,11 +8,9 @@ import ( "log" "os" "strings" - "time" "github.com/ethereum/go-ethereum/common" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/durationpb" ragetypes "github.com/smartcontractkit/libocr/ragep2p/types" @@ -34,126 +32,24 @@ type peer struct { var ( workflowDonPeers = []peer{ { - PeerID: "12D3KooWBCF1XT5Wi8FzfgNCqRL76Swv8TRU3TiD4QiJm8NMNX7N", - Signer: "0x9639dCc7D0ca4468B5f684ef89F12F0B365c9F6d", - EncryptionPublicKey: "0xe7f44e3eedf3527199efec7334183b5384ba0e7c7c57b390b63a3de5a10cd53c", + PeerID: "12D3KooWQXfwA26jysiKKPXKuHcJtWTbGSwzoJxj4rYtEJyQTnFj", + Signer: "0xC44686106b85687F741e1d6182a5e2eD2211a115", + EncryptionPublicKey: "0x0f8b6629bc26321b39dfb7e2bc096584fe43dccfda54b67c24f53fd827efbc72", }, { - PeerID: "12D3KooWG1AyvwmCpZ93J8pBQUE1SuzrjDXnT4BeouncHR3jWLCG", - Signer: "0x8f0fAE64f5f75067833ed5deDC2804B62b21383d", - EncryptionPublicKey: "0x315c6097f89baef3c3ae1503b801aaabf411134ffec66bbe8d1d184540588728", + PeerID: "12D3KooWGCRg5wNKoRFUAypA68ZkwXz8dT5gyF3VdQpEH3FtLqHZ", + Signer: "0x0ee7C8Aa7F8cb5E08415C57B79d7d387F2665E8b", + EncryptionPublicKey: "0x4cb8a297d524469e63e8d8a15c7682891126987acaa39bc4f1db78c066f7af63", }, { - PeerID: "12D3KooWGeUKZBRMbx27FUTgBwZa9Ap9Ym92mywwpuqkEtz8XWyv", - Signer: "0xf09A863D920840c13277e76F43CFBdfB22b8FB7C", - EncryptionPublicKey: "0xa7a5e118213552a939f310e19167f49e9ad952cfe9d51eaae1ad37d92d9f0583", + PeerID: "12D3KooWHggbPfMcSSAwpBZHvwpo2UHzkf1ij3qjTnRiWQ7S5p4g", + Signer: "0xEd850731Be048afE986DaA90Bb482BC3b0f78aec", + EncryptionPublicKey: "0x7a9be509ace5f004fa397b7013893fed13a135dd273f7293dc3c7b6e57f1764e", }, { - PeerID: "12D3KooW9zYWQv3STmDeNDidyzxsJSTxoCTLicafgfeEz9nhwhC4", - Signer: "0x7eD90b519bC3054a575C464dBf39946b53Ff90EF", - EncryptionPublicKey: "0x75f75a86910eed0259e3107b3c368f72c0ad0301bac696fd340916e2437194c3", - }, - { - PeerID: "12D3KooWG1AeBnSJH2mdcDusXQVye2jqodZ6pftTH98HH6xvrE97", - Signer: "0x8F572978673d711b2F061EB7d514BD46EAD6668A", - EncryptionPublicKey: "0xd032f1e884a22fd05151f59565f05a4ccbf984afccbbee13469fc25947e69360", - }, - { - PeerID: "12D3KooWBf3PrkhNoPEmp7iV291YnPuuTsgEDHTscLajxoDvwHGA", - Signer: "0x21eF07Dfaf8f7C10CB0d53D18b641ee690541f9D", - EncryptionPublicKey: "0xed64ed4a2c2954f7390bfdf41a714934c0e55693ad1c0b91505d51f4eb9e4c06", - }, - { - PeerID: "12D3KooWP3FrMTFXXRU2tBC8aYvEBgUX6qhcH9q2JZCUi9Wvc2GX", - Signer: "0x7Fa21F6f716CFaF8f249564D72Ce727253186C89", - EncryptionPublicKey: "0xed64ed4a2c2954f7390bfdf41a714934c0e55693ad1c0b91505d51f4eb9e4c06", - }, - } - triggerDonPeers = []peer{ - { - PeerID: "12D3KooWBaiTbbRwwt2fbNifiL7Ew9tn3vds9AJE3Nf3eaVBX36m", - Signer: "0x9CcE7293a4Cc2621b61193135A95928735e4795F", - EncryptionPublicKey: "0xed64ed4a2c2954f7390bfdf41a714934c0e55693ad1c0b91505d51f4eb9e4c06", - }, - { - PeerID: "12D3KooWS7JSY9fzSfWgbCE1S3W2LNY6ZVpRuun74moVBkKj6utE", - Signer: "0x3c775F20bCB2108C1A818741Ce332Bb5fe0dB925", - EncryptionPublicKey: "0xed64ed4a2c2954f7390bfdf41a714934c0e55693ad1c0b91505d51f4eb9e4c06", - }, - { - PeerID: "12D3KooWMMTDXcWhpVnwrdAer1jnVARTmnr3RyT3v7Djg8ZuoBh9", - Signer: "0x50314239e2CF05555ceeD53E7F47eB2A8Eab0dbB", - EncryptionPublicKey: "0xce0e88d12d568653757f1db154f9c503db3d3d7b37cb03d84b61f39f09824cc0", - }, - { - PeerID: "12D3KooWGzVXsKxXsF4zLgxSDM8Gzx1ywq2pZef4PrHMKuVg4K3P", - Signer: "0xd76A4f98898c3b9A72b244476d7337b50D54BCd8", - EncryptionPublicKey: "0xce0e88d12d568653757f1db154f9c503db3d3d7b37cb03d84b61f39f09824cc0", - }, - { - PeerID: "12D3KooWSyjmmzjVtCzwN7bXzZQFmWiJRuVcKBerNjVgL7HdLJBW", - Signer: "0x656A873f6895b8a03Fb112dE927d43FA54B2c92A", - EncryptionPublicKey: "0x91f11910104ff55209d6d344a15eef6a222a54d4973aaebd301807444b555e3f", - }, - { - PeerID: "12D3KooWLGz9gzhrNsvyM6XnXS3JRkZoQdEzuAvysovnSChNK5ZK", - Signer: "0x5d1e87d87bF2e0cD4Ea64F381a2dbF45e5f0a553", - EncryptionPublicKey: "0x20ff771215e567cf7e9a1fea8f2d4df90adc8303794175f79893037ff8808b51", - }, - { - PeerID: "12D3KooWAvZnvknFAfSiUYjATyhzEJLTeKvAzpcLELHi4ogM3GET", - Signer: "0x91d9b0062265514f012Eb8fABA59372fD9520f56", - EncryptionPublicKey: "0x54176f154052068943569b676fa7eec7dc836e17bbe743ce56b1c7e205191d9c", - }, - } - targetDonPeers = []peer{ - { - PeerID: "12D3KooWJrthXtnPHw7xyHFAxo6NxifYTvc8igKYaA6wRRRqtsMb", - Signer: "0x3F82750353Ea7a051ec9bA011BC628284f9a5327", - EncryptionPublicKey: "0x1a746e0fcaf3e50db87bcc765fbbaee7d24a28166ea1461338a03fcbffb088cf", - }, - { - PeerID: "12D3KooWFQekP9sGex4XhqEJav5EScjTpDVtDqJFg1JvrePBCEGJ", - Signer: "0xc23545876A208AA0443B1b8d552c7be4FF4b53F0", - EncryptionPublicKey: "0x1a746e0fcaf3e50db87bcc765fbbaee7d24a28166ea1461338a03fcbffb088cf", - }, - { - PeerID: "12D3KooWFLEq4hYtdyKWwe47dXGEbSiHMZhmr5xLSJNhpfiEz8NF", - Signer: "0x82601Fa43d8B1dC1d4eB640451aC86a7CDA37011", - EncryptionPublicKey: "0x1a746e0fcaf3e50db87bcc765fbbaee7d24a28166ea1461338a03fcbffb088cf", - }, - { - PeerID: "12D3KooWN2hztiXNNS1jMQTTvvPRYcarK1C7T3Mdqk4x4gwyo5WS", - Signer: "0x1a684B3d8f917fe496b7B1A8b29EDDAED64F649f", - EncryptionPublicKey: "0x1a746e0fcaf3e50db87bcc765fbbaee7d24a28166ea1461338a03fcbffb088cf", - }, - } - - aptosTargetDonPeers = []peer{ - { - PeerID: "12D3KooWNBr1AD3vD3dzSLgg1tK56qyJoenDx7EYNnZpbr1g4jD6", - Signer: "a41f9a561ff2266d94240996a76f9c2b3b7d8184", - EncryptionPublicKey: "0xf28fcfaf2933289b3a98d387f6edf85853df32528c094dee9e737f4ca63e5a30", - }, - { - PeerID: "12D3KooWRRgWiZGw5GYsPa62CkwFNKJb5u4hWo4DinnvjG6GE6Nj", - Signer: "e4f3c7204776530fb7833db6f9dbfdb8bd0ec96892965324a71c20d6776f67f0", - EncryptionPublicKey: "0x49c837675372d8f430e69ccd91c43029600c2c6469a2f933c4a1c4bbbc974c6d", - }, - { - PeerID: "12D3KooWKwzgUHw5YbqUsYUVt3yiLSJcqc8ANofUtqHX6qTm7ox2", - Signer: "4071ea00e2e2c76b3406018ba9f66bf6b9aee3a6762e62ac823b1ee91ba7d7b0", - EncryptionPublicKey: "0x8fe005ef16d57091160c0b4373232e7389c321dff971fc0251a39e360d9ac34a", - }, - { - PeerID: "12D3KooWBRux5o2bw1j3SQwEHzCspjkt7Xe3Y3agRUuab2SUnExj", - Signer: "6f5180c7d276876dbe413bf9b0efff7301d1367f39f4bac64180090cab70989b", - EncryptionPublicKey: "0x90dd41db21351c06396761dd683a82c791cd71e536fce246e582a4ef058091ae", - }, - { - PeerID: "12D3KooWFqvDaMSDGa6eMSTF9en6G2c3ZbGLmaA5Xs3AgxVBPb8B", - Signer: "dbce9a6df8a04d54e52a109d01ee9b5d32873b1d2436cf7b7fae61fd6eca46f8", - EncryptionPublicKey: "0x87cf298dd236a307ea887cd5d81eb0b708e3dd48c984c0700bb26c072e427942", + PeerID: "12D3KooWC44picK3WvVkP1oufHYu1mB18xUg6mF2sNKM8Hc3EmdW", + Signer: "0xD3400B75f2016878dC2013ff2bC2Cf03bD5F19f5", + EncryptionPublicKey: "0x66dcec518809c1dd4ec5c094f651061b974d3cdbf5388cf4f47740c76fb58616", }, } @@ -304,7 +200,7 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { Version: "1.0.0", CapabilityType: uint8(0), // trigger } - sid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, streamsTrigger.LabelledName, streamsTrigger.Version) + _, err = reg.GetHashedCapabilityId(&bind.CallOpts{}, streamsTrigger.LabelledName, streamsTrigger.Version) if err != nil { panic(err) } @@ -334,7 +230,7 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { Version: "1.0.0", CapabilityType: uint8(3), // target } - wid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, writeChain.LabelledName, writeChain.Version) + _, err = reg.GetHashedCapabilityId(&bind.CallOpts{}, writeChain.LabelledName, writeChain.Version) if err != nil { log.Printf("failed to call GetHashedCapabilityId: %s", err) } @@ -344,7 +240,7 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { Version: "1.0.0", CapabilityType: uint8(3), // target } - awid, err := reg.GetHashedCapabilityId(&bind.CallOpts{}, aptosWriteChain.LabelledName, aptosWriteChain.Version) + _, err = reg.GetHashedCapabilityId(&bind.CallOpts{}, aptosWriteChain.LabelledName, aptosWriteChain.Version) if err != nil { log.Printf("failed to call GetHashedCapabilityId: %s", err) } @@ -402,36 +298,6 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { nodes = append(nodes, n) } - for _, triggerPeer := range triggerDonPeers { - n, innerErr := peerToNode(nopID, triggerPeer) - if innerErr != nil { - panic(innerErr) - } - - n.HashedCapabilityIds = [][32]byte{sid} - nodes = append(nodes, n) - } - - for _, targetPeer := range targetDonPeers { - n, innerErr := peerToNode(nopID, targetPeer) - if innerErr != nil { - panic(innerErr) - } - - n.HashedCapabilityIds = [][32]byte{wid} - nodes = append(nodes, n) - } - - for _, targetPeer := range aptosTargetDonPeers { - n, innerErr := peerToNode(nopID, targetPeer) - if innerErr != nil { - panic(innerErr) - } - - n.HashedCapabilityIds = [][32]byte{awid} - nodes = append(nodes, n) - } - tx, err = reg.AddNodes(env.Owner, nodes) if err != nil { log.Printf("failed to AddNodes: %s", err) @@ -476,95 +342,6 @@ func (c *deployAndInitializeCapabilitiesRegistryCommand) Run(args []string) { log.Printf("workflowDON: failed to AddDON: %s", err) } - // trigger DON - ps, err = peers(triggerDonPeers) - if err != nil { - panic(err) - } - - config := &capabilitiespb.CapabilityConfig{ - DefaultConfig: values.Proto(values.EmptyMap()).GetMapValue(), - RemoteConfig: &capabilitiespb.CapabilityConfig_RemoteTriggerConfig{ - RemoteTriggerConfig: &capabilitiespb.RemoteTriggerConfig{ - RegistrationRefresh: durationpb.New(20 * time.Second), - RegistrationExpiry: durationpb.New(60 * time.Second), - // F + 1 - MinResponsesToAggregate: uint32(1) + 1, - }, - }, - } - configb, err := proto.Marshal(config) - if err != nil { - panic(err) - } - cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: sid, - Config: configb, - }, - } - _, err = reg.AddDON(env.Owner, ps, cfgs, true, false, 1) - if err != nil { - log.Printf("triggerDON: failed to AddDON: %s", err) - } - - // target DON - ps, err = peers(targetDonPeers) - if err != nil { - panic(err) - } - - targetCapabilityConfig := newCapabilityConfig() - targetCapabilityConfig.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTargetConfig{ - RemoteTargetConfig: &capabilitiespb.RemoteTargetConfig{ - RequestHashExcludedAttributes: []string{"signed_report.Signatures"}, - }, - } - - remoteTargetConfigBytes, err := proto.Marshal(targetCapabilityConfig) - if err != nil { - panic(err) - } - - cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: wid, - Config: remoteTargetConfigBytes, - }, - } - _, err = reg.AddDON(env.Owner, ps, cfgs, true, false, 1) - if err != nil { - log.Printf("targetDON: failed to AddDON: %s", err) - } - - // Aptos target DON - ps, err = peers(aptosTargetDonPeers) - if err != nil { - panic(err) - } - - targetCapabilityConfig = newCapabilityConfig() - targetCapabilityConfig.RemoteConfig = &capabilitiespb.CapabilityConfig_RemoteTargetConfig{ - RemoteTargetConfig: &capabilitiespb.RemoteTargetConfig{ - RequestHashExcludedAttributes: []string{"signed_report.Signatures"}, - }, - } - - remoteTargetConfigBytes, err = proto.Marshal(targetCapabilityConfig) - if err != nil { - panic(err) - } - - cfgs = []kcr.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: awid, - Config: remoteTargetConfigBytes, - }, - } - _, err = reg.AddDON(env.Owner, ps, cfgs, true, false, 1) - if err != nil { - log.Printf("targetDON: failed to AddDON: %s", err) - } } func deployCapabilitiesRegistry(env helpers.Environment) *kcr.CapabilitiesRegistry { diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index 68b9b99a823..ac0d28760eb 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -330,6 +330,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { return relayer.NewContractReader(ctx, bytes) }, eventHandler) + globalLogger.Debugw("Creating WorkflowRegistrySyncer") wfSyncer := syncer.NewWorkflowRegistry(globalLogger, func(ctx context.Context, bytes []byte) (syncer.ContractReader, error) { return relayer.NewContractReader(ctx, bytes) }, cfg.Capabilities().WorkflowRegistry().Address(), diff --git a/core/services/feeds/service.go b/core/services/feeds/service.go index 509ca19ae6a..852bab6c63a 100644 --- a/core/services/feeds/service.go +++ b/core/services/feeds/service.go @@ -128,6 +128,7 @@ type service struct { p2pKeyStore keystore.P2P ocr1KeyStore keystore.OCR ocr2KeyStore keystore.OCR2 + workflowKeyStore keystore.Workflow jobSpawner job.Spawner gCfg GeneralConfig featCfg FeatureConfig @@ -170,6 +171,7 @@ func NewService( csaKeyStore: keyStore.CSA(), ocr1KeyStore: keyStore.OCR(), ocr2KeyStore: keyStore.OCR2(), + workflowKeyStore: keyStore.Workflow(), gCfg: gCfg, featCfg: fCfg, insecureCfg: insecureCfg, @@ -277,9 +279,11 @@ func (s *service) SyncNodeInfo(ctx context.Context, id int64) error { cfgMsgs = append(cfgMsgs, cfgMsg) } + workflowKey := s.getWorkflowPublicKey() if _, err = fmsClient.UpdateNode(ctx, &pb.UpdateNodeRequest{ Version: s.version, ChainConfigs: cfgMsgs, + WorkflowKey: &workflowKey, }); err != nil { return err } @@ -1173,6 +1177,20 @@ func (s *service) getCSAPrivateKey() (privkey []byte, err error) { return keys[0].Raw(), nil } +// getWorkflowPublicKey retrieves the server's Workflow public key. +// Since there will be at most one key, it returns the first key found. +// If an error occurs or no keys are found, it returns blank. +func (s *service) getWorkflowPublicKey() string { + keys, err := s.workflowKeyStore.GetAll() + if err != nil { + return "" + } + if len(keys) < 1 { + return "" + } + return keys[0].PublicKeyString() +} + // observeJobProposalCounts is a helper method that queries the repository for the count of // job proposals by status and then updates prometheus gauges. func (s *service) observeJobProposalCounts(ctx context.Context) error { diff --git a/core/services/feeds/service_test.go b/core/services/feeds/service_test.go index 57dbc689d4f..c513c05562d 100644 --- a/core/services/feeds/service_test.go +++ b/core/services/feeds/service_test.go @@ -40,6 +40,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocrkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" ksmocks "github.com/smartcontractkit/chainlink/v2/core/services/keystore/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" "github.com/smartcontractkit/chainlink/v2/core/services/versioning" @@ -183,16 +184,17 @@ chainID = 1337 type TestService struct { feeds.Service - orm *mocks.ORM - jobORM *jobmocks.ORM - connMgr *mocks.ConnectionsManager - spawner *jobmocks.Spawner - fmsClient *mocks.FeedsManagerClient - csaKeystore *ksmocks.CSA - p2pKeystore *ksmocks.P2P - ocr1Keystore *ksmocks.OCR - ocr2Keystore *ksmocks.OCR2 - legacyChains legacyevm.LegacyChainContainer + orm *mocks.ORM + jobORM *jobmocks.ORM + connMgr *mocks.ConnectionsManager + spawner *jobmocks.Spawner + fmsClient *mocks.FeedsManagerClient + csaKeystore *ksmocks.CSA + p2pKeystore *ksmocks.P2P + ocr1Keystore *ksmocks.OCR + ocr2Keystore *ksmocks.OCR2 + workflowKeystore *ksmocks.Workflow + legacyChains legacyevm.LegacyChainContainer } func setupTestService(t *testing.T) *TestService { @@ -205,15 +207,16 @@ func setupTestServiceCfg(t *testing.T, overrideCfg func(c *chainlink.Config, s * t.Helper() var ( - orm = mocks.NewORM(t) - jobORM = jobmocks.NewORM(t) - connMgr = mocks.NewConnectionsManager(t) - spawner = jobmocks.NewSpawner(t) - fmsClient = mocks.NewFeedsManagerClient(t) - csaKeystore = ksmocks.NewCSA(t) - p2pKeystore = ksmocks.NewP2P(t) - ocr1Keystore = ksmocks.NewOCR(t) - ocr2Keystore = ksmocks.NewOCR2(t) + orm = mocks.NewORM(t) + jobORM = jobmocks.NewORM(t) + connMgr = mocks.NewConnectionsManager(t) + spawner = jobmocks.NewSpawner(t) + fmsClient = mocks.NewFeedsManagerClient(t) + csaKeystore = ksmocks.NewCSA(t) + p2pKeystore = ksmocks.NewP2P(t) + ocr1Keystore = ksmocks.NewOCR(t) + ocr2Keystore = ksmocks.NewOCR2(t) + workflowKeystore = ksmocks.NewWorkflow(t) ) lggr := logger.TestLogger(t) @@ -229,21 +232,23 @@ func setupTestServiceCfg(t *testing.T, overrideCfg func(c *chainlink.Config, s * keyStore.On("P2P").Return(p2pKeystore) keyStore.On("OCR").Return(ocr1Keystore) keyStore.On("OCR2").Return(ocr2Keystore) + keyStore.On("Workflow").Return(workflowKeystore) svc := feeds.NewService(orm, jobORM, db, spawner, keyStore, gcfg, gcfg.Feature(), gcfg.Insecure(), gcfg.JobPipeline(), gcfg.OCR(), gcfg.OCR2(), legacyChains, lggr, "1.0.0", nil) svc.SetConnectionsManager(connMgr) return &TestService{ - Service: svc, - orm: orm, - jobORM: jobORM, - connMgr: connMgr, - spawner: spawner, - fmsClient: fmsClient, - csaKeystore: csaKeystore, - p2pKeystore: p2pKeystore, - ocr1Keystore: ocr1Keystore, - ocr2Keystore: ocr2Keystore, - legacyChains: legacyChains, + Service: svc, + orm: orm, + jobORM: jobORM, + connMgr: connMgr, + spawner: spawner, + fmsClient: fmsClient, + csaKeystore: csaKeystore, + p2pKeystore: p2pKeystore, + ocr1Keystore: ocr1Keystore, + ocr2Keystore: ocr2Keystore, + workflowKeystore: workflowKeystore, + legacyChains: legacyChains, } } @@ -613,10 +618,15 @@ func Test_Service_CreateChainConfig(t *testing.T) { svc = setupTestService(t) ) + workflowKey, err := workflowkey.New() + require.NoError(t, err) + svc.workflowKeystore.On("GetAll").Return([]workflowkey.Key{workflowKey}, nil) + svc.orm.On("CreateChainConfig", mock.Anything, cfg).Return(int64(1), nil) svc.orm.On("GetManager", mock.Anything, mgr.ID).Return(&mgr, nil) svc.connMgr.On("GetClient", mgr.ID).Return(svc.fmsClient, nil) svc.orm.On("ListChainConfigsByManagerIDs", mock.Anything, []int64{mgr.ID}).Return([]feeds.ChainConfig{cfg}, nil) + wkID := workflowKey.ID() svc.fmsClient.On("UpdateNode", mock.Anything, &proto.UpdateNodeRequest{ Version: nodeVersion.Version, ChainConfigs: []*proto.ChainConfig{ @@ -633,6 +643,7 @@ func Test_Service_CreateChainConfig(t *testing.T) { Ocr2Config: &proto.OCR2Config{Enabled: false}, }, }, + WorkflowKey: &wkID, }).Return(&proto.UpdateNodeResponse{}, nil) actual, err := svc.CreateChainConfig(testutils.Context(t), cfg) @@ -677,14 +688,20 @@ func Test_Service_DeleteChainConfig(t *testing.T) { svc = setupTestService(t) ) + workflowKey, err := workflowkey.New() + require.NoError(t, err) + svc.workflowKeystore.On("GetAll").Return([]workflowkey.Key{workflowKey}, nil) + svc.orm.On("GetChainConfig", mock.Anything, cfg.ID).Return(&cfg, nil) svc.orm.On("DeleteChainConfig", mock.Anything, cfg.ID).Return(cfg.ID, nil) svc.orm.On("GetManager", mock.Anything, mgr.ID).Return(&mgr, nil) svc.connMgr.On("GetClient", mgr.ID).Return(svc.fmsClient, nil) svc.orm.On("ListChainConfigsByManagerIDs", mock.Anything, []int64{mgr.ID}).Return([]feeds.ChainConfig{}, nil) + wkID := workflowKey.ID() svc.fmsClient.On("UpdateNode", mock.Anything, &proto.UpdateNodeRequest{ Version: nodeVersion.Version, ChainConfigs: []*proto.ChainConfig{}, + WorkflowKey: &wkID, }).Return(&proto.UpdateNodeResponse{}, nil) actual, err := svc.DeleteChainConfig(testutils.Context(t), cfg.ID) @@ -762,10 +779,15 @@ func Test_Service_UpdateChainConfig(t *testing.T) { svc = setupTestService(t) ) + workflowKey, err := workflowkey.New() + require.NoError(t, err) + svc.workflowKeystore.On("GetAll").Return([]workflowkey.Key{workflowKey}, nil) + svc.orm.On("UpdateChainConfig", mock.Anything, cfg).Return(int64(1), nil) svc.orm.On("GetChainConfig", mock.Anything, cfg.ID).Return(&cfg, nil) svc.connMgr.On("GetClient", mgr.ID).Return(svc.fmsClient, nil) svc.orm.On("ListChainConfigsByManagerIDs", mock.Anything, []int64{mgr.ID}).Return([]feeds.ChainConfig{cfg}, nil) + wkID := workflowKey.ID() svc.fmsClient.On("UpdateNode", mock.Anything, &proto.UpdateNodeRequest{ Version: nodeVersion.Version, ChainConfigs: []*proto.ChainConfig{ @@ -782,6 +804,7 @@ func Test_Service_UpdateChainConfig(t *testing.T) { Ocr2Config: &proto.OCR2Config{Enabled: false}, }, }, + WorkflowKey: &wkID, }).Return(&proto.UpdateNodeResponse{}, nil) actual, err := svc.UpdateChainConfig(testutils.Context(t), cfg) @@ -1707,6 +1730,9 @@ func Test_Service_SyncNodeInfo(t *testing.T) { ocrKey, err := ocrkey.NewV2() require.NoError(t, err) + workflowKey, err := workflowkey.New() + require.NoError(t, err) + var ( multiaddr = "/dns4/chain.link/tcp/1234/p2p/16Uiu2HAm58SP7UL8zsnpeuwHfytLocaqgnyaYKP8wu7qRdrixLju" mgr = &feeds.FeedsManager{ID: 1} @@ -1754,6 +1780,8 @@ func Test_Service_SyncNodeInfo(t *testing.T) { svc.p2pKeystore.On("Get", p2pKey.PeerID()).Return(p2pKey, nil) svc.ocr1Keystore.On("Get", ocrKey.GetID()).Return(ocrKey, nil) + svc.workflowKeystore.On("GetAll").Return([]workflowkey.Key{workflowKey}, nil) + wkID := workflowKey.ID() svc.fmsClient.On("UpdateNode", mock.Anything, &proto.UpdateNodeRequest{ Version: nodeVersion.Version, ChainConfigs: []*proto.ChainConfig{ @@ -1794,6 +1822,7 @@ func Test_Service_SyncNodeInfo(t *testing.T) { }, }, }, + WorkflowKey: &wkID, }).Return(&proto.UpdateNodeResponse{}, nil) err = svc.SyncNodeInfo(testutils.Context(t), mgr.ID) diff --git a/core/services/keystore/keys/ocr2key/key_bundle.go b/core/services/keystore/keys/ocr2key/key_bundle.go index 2c25a159fef..a08bd84ac30 100644 --- a/core/services/keystore/keys/ocr2key/key_bundle.go +++ b/core/services/keystore/keys/ocr2key/key_bundle.go @@ -19,7 +19,6 @@ type OCR3SignerVerifier interface { Verify3(publicKey ocrtypes.OnchainPublicKey, cd ocrtypes.ConfigDigest, seqNr uint64, r ocrtypes.Report, signature []byte) bool } -// nolint type KeyBundle interface { // OnchainKeyring is used for signing reports (groups of observations, verified onchain) ocrtypes.OnchainKeyring @@ -108,7 +107,6 @@ func (kb keyBundleBase) GoString() string { return kb.String() } -// nolint type Raw []byte func (raw Raw) Key() (kb KeyBundle) { diff --git a/core/services/keystore/keys/workflowkey/key.go b/core/services/keystore/keys/workflowkey/key.go index ce8560303e0..084878a5ee3 100644 --- a/core/services/keystore/keys/workflowkey/key.go +++ b/core/services/keystore/keys/workflowkey/key.go @@ -50,10 +50,18 @@ func New() (Key, error) { } func (k Key) PublicKey() [curve25519.PointSize]byte { + if k.publicKey == nil { + return [curve25519.PointSize]byte{} + } + return *k.publicKey } func (k Key) PublicKeyString() string { + if k.publicKey == nil { + return "" + } + return hex.EncodeToString(k.publicKey[:]) } @@ -78,6 +86,10 @@ func (k Key) GoString() string { // Encrypt encrypts a message using the public key func (k Key) Encrypt(plaintext []byte) ([]byte, error) { publicKey := k.PublicKey() + if publicKey == [curve25519.PointSize]byte{} { + return nil, errors.New("public key is empty") + } + encrypted, err := box.SealAnonymous(nil, plaintext, &publicKey, cryptorand.Reader) if err != nil { return nil, err @@ -89,6 +101,10 @@ func (k Key) Encrypt(plaintext []byte) ([]byte, error) { // Decrypt decrypts a message that was encrypted using the private key func (k Key) Decrypt(ciphertext []byte) (plaintext []byte, err error) { publicKey := k.PublicKey() + if publicKey == [curve25519.PointSize]byte{} { + return nil, errors.New("public key is empty") + } + decrypted, success := box.OpenAnonymous(nil, ciphertext, &publicKey, k.privateKey) if !success { return nil, errors.New("decryption failed") diff --git a/core/services/keystore/keystoretest.go b/core/services/keystore/keystoretest.go index e179b51bb54..626cc4bab99 100644 --- a/core/services/keystore/keystoretest.go +++ b/core/services/keystore/keystoretest.go @@ -75,5 +75,6 @@ func NewInMemory(ds sqlutil.DataSource, scryptParams utils.ScryptParams, lggr lo starknet: newStarkNetKeyStore(km), aptos: newAptosKeyStore(km), vrf: newVRFKeyStore(km), + workflow: newWorkflowKeyStore(km), } } diff --git a/core/services/keystore/mocks/workflow.go b/core/services/keystore/mocks/workflow.go new file mode 100644 index 00000000000..f19045cecc4 --- /dev/null +++ b/core/services/keystore/mocks/workflow.go @@ -0,0 +1,474 @@ +// Code generated by mockery v2.46.3. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + workflowkey "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" +) + +// Workflow is an autogenerated mock type for the Workflow type +type Workflow struct { + mock.Mock +} + +type Workflow_Expecter struct { + mock *mock.Mock +} + +func (_m *Workflow) EXPECT() *Workflow_Expecter { + return &Workflow_Expecter{mock: &_m.Mock} +} + +// Add provides a mock function with given fields: ctx, key +func (_m *Workflow) Add(ctx context.Context, key workflowkey.Key) error { + ret := _m.Called(ctx, key) + + if len(ret) == 0 { + panic("no return value specified for Add") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, workflowkey.Key) error); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Workflow_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add' +type Workflow_Add_Call struct { + *mock.Call +} + +// Add is a helper method to define mock.On call +// - ctx context.Context +// - key workflowkey.Key +func (_e *Workflow_Expecter) Add(ctx interface{}, key interface{}) *Workflow_Add_Call { + return &Workflow_Add_Call{Call: _e.mock.On("Add", ctx, key)} +} + +func (_c *Workflow_Add_Call) Run(run func(ctx context.Context, key workflowkey.Key)) *Workflow_Add_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(workflowkey.Key)) + }) + return _c +} + +func (_c *Workflow_Add_Call) Return(_a0 error) *Workflow_Add_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Workflow_Add_Call) RunAndReturn(run func(context.Context, workflowkey.Key) error) *Workflow_Add_Call { + _c.Call.Return(run) + return _c +} + +// Create provides a mock function with given fields: ctx +func (_m *Workflow) Create(ctx context.Context) (workflowkey.Key, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Create") + } + + var r0 workflowkey.Key + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (workflowkey.Key, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) workflowkey.Key); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(workflowkey.Key) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Workflow_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' +type Workflow_Create_Call struct { + *mock.Call +} + +// Create is a helper method to define mock.On call +// - ctx context.Context +func (_e *Workflow_Expecter) Create(ctx interface{}) *Workflow_Create_Call { + return &Workflow_Create_Call{Call: _e.mock.On("Create", ctx)} +} + +func (_c *Workflow_Create_Call) Run(run func(ctx context.Context)) *Workflow_Create_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Workflow_Create_Call) Return(_a0 workflowkey.Key, _a1 error) *Workflow_Create_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Workflow_Create_Call) RunAndReturn(run func(context.Context) (workflowkey.Key, error)) *Workflow_Create_Call { + _c.Call.Return(run) + return _c +} + +// Delete provides a mock function with given fields: ctx, id +func (_m *Workflow) Delete(ctx context.Context, id string) (workflowkey.Key, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 workflowkey.Key + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (workflowkey.Key, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) workflowkey.Key); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(workflowkey.Key) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Workflow_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type Workflow_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *Workflow_Expecter) Delete(ctx interface{}, id interface{}) *Workflow_Delete_Call { + return &Workflow_Delete_Call{Call: _e.mock.On("Delete", ctx, id)} +} + +func (_c *Workflow_Delete_Call) Run(run func(ctx context.Context, id string)) *Workflow_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *Workflow_Delete_Call) Return(_a0 workflowkey.Key, _a1 error) *Workflow_Delete_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Workflow_Delete_Call) RunAndReturn(run func(context.Context, string) (workflowkey.Key, error)) *Workflow_Delete_Call { + _c.Call.Return(run) + return _c +} + +// EnsureKey provides a mock function with given fields: ctx +func (_m *Workflow) EnsureKey(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for EnsureKey") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Workflow_EnsureKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnsureKey' +type Workflow_EnsureKey_Call struct { + *mock.Call +} + +// EnsureKey is a helper method to define mock.On call +// - ctx context.Context +func (_e *Workflow_Expecter) EnsureKey(ctx interface{}) *Workflow_EnsureKey_Call { + return &Workflow_EnsureKey_Call{Call: _e.mock.On("EnsureKey", ctx)} +} + +func (_c *Workflow_EnsureKey_Call) Run(run func(ctx context.Context)) *Workflow_EnsureKey_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *Workflow_EnsureKey_Call) Return(_a0 error) *Workflow_EnsureKey_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Workflow_EnsureKey_Call) RunAndReturn(run func(context.Context) error) *Workflow_EnsureKey_Call { + _c.Call.Return(run) + return _c +} + +// Export provides a mock function with given fields: id, password +func (_m *Workflow) Export(id string, password string) ([]byte, error) { + ret := _m.Called(id, password) + + if len(ret) == 0 { + panic("no return value specified for Export") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(string, string) ([]byte, error)); ok { + return rf(id, password) + } + if rf, ok := ret.Get(0).(func(string, string) []byte); ok { + r0 = rf(id, password) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(id, password) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Workflow_Export_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Export' +type Workflow_Export_Call struct { + *mock.Call +} + +// Export is a helper method to define mock.On call +// - id string +// - password string +func (_e *Workflow_Expecter) Export(id interface{}, password interface{}) *Workflow_Export_Call { + return &Workflow_Export_Call{Call: _e.mock.On("Export", id, password)} +} + +func (_c *Workflow_Export_Call) Run(run func(id string, password string)) *Workflow_Export_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string)) + }) + return _c +} + +func (_c *Workflow_Export_Call) Return(_a0 []byte, _a1 error) *Workflow_Export_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Workflow_Export_Call) RunAndReturn(run func(string, string) ([]byte, error)) *Workflow_Export_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function with given fields: id +func (_m *Workflow) Get(id string) (workflowkey.Key, error) { + ret := _m.Called(id) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 workflowkey.Key + var r1 error + if rf, ok := ret.Get(0).(func(string) (workflowkey.Key, error)); ok { + return rf(id) + } + if rf, ok := ret.Get(0).(func(string) workflowkey.Key); ok { + r0 = rf(id) + } else { + r0 = ret.Get(0).(workflowkey.Key) + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Workflow_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type Workflow_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - id string +func (_e *Workflow_Expecter) Get(id interface{}) *Workflow_Get_Call { + return &Workflow_Get_Call{Call: _e.mock.On("Get", id)} +} + +func (_c *Workflow_Get_Call) Run(run func(id string)) *Workflow_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Workflow_Get_Call) Return(_a0 workflowkey.Key, _a1 error) *Workflow_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Workflow_Get_Call) RunAndReturn(run func(string) (workflowkey.Key, error)) *Workflow_Get_Call { + _c.Call.Return(run) + return _c +} + +// GetAll provides a mock function with given fields: +func (_m *Workflow) GetAll() ([]workflowkey.Key, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetAll") + } + + var r0 []workflowkey.Key + var r1 error + if rf, ok := ret.Get(0).(func() ([]workflowkey.Key, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []workflowkey.Key); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]workflowkey.Key) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Workflow_GetAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAll' +type Workflow_GetAll_Call struct { + *mock.Call +} + +// GetAll is a helper method to define mock.On call +func (_e *Workflow_Expecter) GetAll() *Workflow_GetAll_Call { + return &Workflow_GetAll_Call{Call: _e.mock.On("GetAll")} +} + +func (_c *Workflow_GetAll_Call) Run(run func()) *Workflow_GetAll_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Workflow_GetAll_Call) Return(_a0 []workflowkey.Key, _a1 error) *Workflow_GetAll_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Workflow_GetAll_Call) RunAndReturn(run func() ([]workflowkey.Key, error)) *Workflow_GetAll_Call { + _c.Call.Return(run) + return _c +} + +// Import provides a mock function with given fields: ctx, keyJSON, password +func (_m *Workflow) Import(ctx context.Context, keyJSON []byte, password string) (workflowkey.Key, error) { + ret := _m.Called(ctx, keyJSON, password) + + if len(ret) == 0 { + panic("no return value specified for Import") + } + + var r0 workflowkey.Key + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte, string) (workflowkey.Key, error)); ok { + return rf(ctx, keyJSON, password) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte, string) workflowkey.Key); ok { + r0 = rf(ctx, keyJSON, password) + } else { + r0 = ret.Get(0).(workflowkey.Key) + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte, string) error); ok { + r1 = rf(ctx, keyJSON, password) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Workflow_Import_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Import' +type Workflow_Import_Call struct { + *mock.Call +} + +// Import is a helper method to define mock.On call +// - ctx context.Context +// - keyJSON []byte +// - password string +func (_e *Workflow_Expecter) Import(ctx interface{}, keyJSON interface{}, password interface{}) *Workflow_Import_Call { + return &Workflow_Import_Call{Call: _e.mock.On("Import", ctx, keyJSON, password)} +} + +func (_c *Workflow_Import_Call) Run(run func(ctx context.Context, keyJSON []byte, password string)) *Workflow_Import_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]byte), args[2].(string)) + }) + return _c +} + +func (_c *Workflow_Import_Call) Return(_a0 workflowkey.Key, _a1 error) *Workflow_Import_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Workflow_Import_Call) RunAndReturn(run func(context.Context, []byte, string) (workflowkey.Key, error)) *Workflow_Import_Call { + _c.Call.Return(run) + return _c +} + +// NewWorkflow creates a new instance of Workflow. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewWorkflow(t interface { + mock.TestingT + Cleanup(func()) +}) *Workflow { + mock := &Workflow{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/keystore/models.go b/core/services/keystore/models.go index e0b53ef95e4..151934827c3 100644 --- a/core/services/keystore/models.go +++ b/core/services/keystore/models.go @@ -239,6 +239,9 @@ func (kr *keyRing) raw() (rawKeys rawKeyRing) { for _, vrfKey := range kr.VRF { rawKeys.VRF = append(rawKeys.VRF, vrfKey.Raw()) } + for _, workflowKey := range kr.Workflow { + rawKeys.Workflow = append(rawKeys.Workflow, workflowKey.Raw()) + } return rawKeys } @@ -284,6 +287,12 @@ func (kr *keyRing) logPubKeys(lggr logger.Logger) { for _, VRFKey := range kr.VRF { vrfIDs = append(vrfIDs, VRFKey.ID()) } + workflowIDs := make([]string, len(kr.Workflow)) + i := 0 + for _, workflowKey := range kr.Workflow { + workflowIDs[i] = workflowKey.ID() + i++ + } if len(csaIDs) > 0 { lggr.Infow(fmt.Sprintf("Unlocked %d CSA keys", len(csaIDs)), "keys", csaIDs) } @@ -314,6 +323,9 @@ func (kr *keyRing) logPubKeys(lggr logger.Logger) { if len(vrfIDs) > 0 { lggr.Infow(fmt.Sprintf("Unlocked %d VRF keys", len(vrfIDs)), "keys", vrfIDs) } + if len(workflowIDs) > 0 { + lggr.Infow(fmt.Sprintf("Unlocked %d Workflow keys", len(workflowIDs)), "keys", workflowIDs) + } if len(kr.LegacyKeys.legacyRawKeys) > 0 { lggr.Infow(fmt.Sprintf("%d keys stored in legacy system", kr.LegacyKeys.legacyRawKeys.len())) } @@ -333,6 +345,7 @@ type rawKeyRing struct { StarkNet []starkkey.Raw Aptos []aptoskey.Raw VRF []vrfkey.Raw + Workflow []workflowkey.Raw LegacyKeys LegacyKeyStorage `json:"-"` } @@ -379,6 +392,10 @@ func (rawKeys rawKeyRing) keys() (*keyRing, error) { vrfKey := rawVRFKey.Key() keyRing.VRF[vrfKey.ID()] = vrfKey } + for _, rawWorkflowKey := range rawKeys.Workflow { + workflowKey := rawWorkflowKey.Key() + keyRing.Workflow[workflowKey.ID()] = workflowKey + } keyRing.LegacyKeys = rawKeys.LegacyKeys return keyRing, nil diff --git a/core/services/keystore/workflow_test.go b/core/services/keystore/workflow_test.go index 051ebdb76a7..d7e540b9c75 100644 --- a/core/services/keystore/workflow_test.go +++ b/core/services/keystore/workflow_test.go @@ -175,4 +175,39 @@ func Test_EncryptionKeyStore_E2E(t *testing.T) { require.NoError(t, err) require.Len(t, keys, 1) }) + + t.Run("persists keys across restarts", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + + keys, err := ks.GetAll() + require.NoError(t, err) + assert.Empty(t, keys) + + err = keyStore.Workflow().EnsureKey(ctx) + require.NoError(t, err) + + keys, err = ks.GetAll() + require.NoError(t, err) + + require.NoError(t, err) + require.Len(t, keys, 1) + + // Now instantiate the keystore again, but with the same DB + // This should fetch the key directly from the DB. + keyStore := keystore.ExposedNewMaster(t, db) + require.NoError(t, keyStore.Unlock(testutils.Context(t), cltest.Password)) + + gotKeys, err := keyStore.Workflow().GetAll() + require.NoError(t, err) + require.Len(t, gotKeys, 1) + + assert.Equal(t, keys[0].PublicKeyString(), gotKeys[0].PublicKeyString()) + + err = keyStore.Workflow().EnsureKey(testutils.Context(t)) + require.NoError(t, err) + gotKeys, err = keyStore.Workflow().GetAll() + require.NoError(t, err) + require.Len(t, gotKeys, 1) + }) } diff --git a/core/services/llo/evm/report_codec_premium_legacy.go b/core/services/llo/evm/report_codec_premium_legacy.go index e38f6db7781..fdbad6aead9 100644 --- a/core/services/llo/evm/report_codec_premium_legacy.go +++ b/core/services/llo/evm/report_codec_premium_legacy.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "math/big" "github.com/ethereum/go-ethereum/common" @@ -14,11 +15,11 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2/types" ocr2types "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo" v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3" "github.com/smartcontractkit/chainlink-data-streams/llo" - "github.com/smartcontractkit/chainlink-common/pkg/logger" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury" reportcodecv3 "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/mercury/v3/reportcodec" @@ -121,7 +122,10 @@ func (r ReportCodecPremiumLegacy) Pack(digest types.ConfigDigest, seqNr uint64, ss = append(ss, s) vs[i] = v } - reportCtx := LegacyReportContext(digest, seqNr, r.donID) + reportCtx, err := LegacyReportContext(digest, seqNr, r.donID) + if err != nil { + return nil, fmt.Errorf("failed to get legacy report context: %w", err) + } rawReportCtx := evmutil.RawReportContext(reportCtx) payload, err := mercury.PayloadTypes.Pack(rawReportCtx, []byte(report), rs, ss, vs) @@ -203,21 +207,28 @@ func LLOExtraHash(donID uint32) common.Hash { return common.BigToHash(new(big.Int).SetUint64(combined)) } -func SeqNrToEpochAndRound(seqNr uint64) (epoch uint32, round uint8) { +func SeqNrToEpochAndRound(seqNr uint64) (epoch uint32, round uint8, err error) { // Simulate 256 rounds/epoch - epoch = uint32(seqNr / 256) // nolint - round = uint8(seqNr % 256) // nolint + if seqNr/256 > math.MaxUint32 { + err = fmt.Errorf("epoch overflows uint32: %d", seqNr) + return + } + epoch = uint32(seqNr / 256) //nolint:gosec // G115 false positive + round = uint8(seqNr % 256) //nolint:gosec // G115 false positive return } -func LegacyReportContext(cd ocr2types.ConfigDigest, seqNr uint64, donID uint32) ocr2types.ReportContext { - epoch, round := SeqNrToEpochAndRound(seqNr) +func LegacyReportContext(cd ocr2types.ConfigDigest, seqNr uint64, donID uint32) (ocr2types.ReportContext, error) { + epoch, round, err := SeqNrToEpochAndRound(seqNr) + if err != nil { + return ocr2types.ReportContext{}, err + } return ocr2types.ReportContext{ ReportTimestamp: ocr2types.ReportTimestamp{ ConfigDigest: cd, - Epoch: uint32(epoch), - Round: uint8(round), + Epoch: epoch, + Round: round, }, ExtraHash: LLOExtraHash(donID), // ExtraHash is always zero for mercury, we use LLOExtraHash here to differentiate from the legacy plugin - } + }, nil } diff --git a/core/services/llo/keyring.go b/core/services/llo/keyring.go index dee223b4531..d4bf615711c 100644 --- a/core/services/llo/keyring.go +++ b/core/services/llo/keyring.go @@ -84,7 +84,10 @@ func (okr *onchainKeyring) Sign(digest types.ConfigDigest, seqNr uint64, r ocr3t rf := r.Info.ReportFormat if key, exists := okr.keys[rf]; exists { // NOTE: Must use legacy Sign method for compatibility with v0.3 report verification - rc := evm.LegacyReportContext(digest, seqNr, okr.donID) + rc, err := evm.LegacyReportContext(digest, seqNr, okr.donID) + if err != nil { + return nil, fmt.Errorf("failed to get legacy report context: %w", err) + } return key.Sign(rc, r.Report) } default: @@ -102,7 +105,11 @@ func (okr *onchainKeyring) Verify(key types.OnchainPublicKey, digest types.Confi rf := r.Info.ReportFormat if verifier, exists := okr.keys[rf]; exists { // NOTE: Must use legacy Verify method for compatibility with v0.3 report verification - rc := evm.LegacyReportContext(digest, seqNr, okr.donID) + rc, err := evm.LegacyReportContext(digest, seqNr, okr.donID) + if err != nil { + okr.lggr.Errorw("Verify failed; unable to get legacy report context", "err", err) + return false + } return verifier.Verify(key, rc, r.Report, signature) } default: diff --git a/core/services/llo/keyring_test.go b/core/services/llo/keyring_test.go index 3a0f8c5650b..b50e8d169c0 100644 --- a/core/services/llo/keyring_test.go +++ b/core/services/llo/keyring_test.go @@ -2,13 +2,14 @@ package llo import ( "fmt" - "math/rand" + "math" + "math/rand/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - ocr3types "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" @@ -83,7 +84,7 @@ func Test_Keyring(t *testing.T) { cd, err := ocrtypes.BytesToConfigDigest(testutils.MustRandBytes(32)) require.NoError(t, err) - seqNr := rand.Uint64() + seqNr := rand.Uint64N(math.MaxUint32 << 8) t.Run("Sign+Verify", func(t *testing.T) { for _, tc := range cases { t.Run(tc.format.String(), func(t *testing.T) { diff --git a/core/services/llo/mercurytransmitter/orm.go b/core/services/llo/mercurytransmitter/orm.go index ed8c8843f4c..045161395b5 100644 --- a/core/services/llo/mercurytransmitter/orm.go +++ b/core/services/llo/mercurytransmitter/orm.go @@ -72,7 +72,7 @@ func (o *orm) Insert(ctx context.Context, transmissions []*Transmission) error { DonID: o.donID, ServerURL: t.ServerURL, ConfigDigest: t.ConfigDigest, - SeqNr: int64(t.SeqNr), //nolint + SeqNr: int64(t.SeqNr), //nolint:gosec // G115 false positive Report: t.Report.Report, LifecycleStage: string(t.Report.Info.LifeCycleStage), ReportFormat: uint32(t.Report.Info.ReportFormat), @@ -162,7 +162,7 @@ func (o *orm) Get(ctx context.Context, serverURL string) ([]*Transmission, error } transmission.Sigs = append(transmission.Sigs, ocrtypes.AttributedOnchainSignature{ Signature: sig, - Signer: commontypes.OracleID(signers[i]), //nolint + Signer: commontypes.OracleID(signers[i]), //nolint:gosec // G115 false positive }) } diff --git a/core/services/llo/onchain_channel_definition_cache_test.go b/core/services/llo/onchain_channel_definition_cache_test.go index fa5a26237e5..e6ea3264273 100644 --- a/core/services/llo/onchain_channel_definition_cache_test.go +++ b/core/services/llo/onchain_channel_definition_cache_test.go @@ -298,7 +298,7 @@ func Test_ChannelDefinitionCache(t *testing.T) { } t.Run("nil ctx returns error", func(t *testing.T) { - _, err := cdc.fetchChannelDefinitions(nil, "notvalid://foos", [32]byte{}) //nolint + _, err := cdc.fetchChannelDefinitions(nil, "notvalid://foos", [32]byte{}) //nolint:staticcheck // SA1012 we pass nil intentionally here assert.EqualError(t, err, "failed to create http.Request; net/http: nil Context") }) diff --git a/core/services/llo/telemetry.go b/core/services/llo/telemetry.go index bb86679dc52..0b315d78d2b 100644 --- a/core/services/llo/telemetry.go +++ b/core/services/llo/telemetry.go @@ -119,7 +119,6 @@ func (t *telemeter) collectV3PremiumLegacyTelemetry(d TelemetryObservation) { askPrice = v.Ask.IntPart() ask = v.Ask.String() } - epoch, round := evm.SeqNrToEpochAndRound(d.opts.OutCtx().SeqNr) tea := &telem.EnhancedEAMercury{ DataSource: eaTelem.DataSource, DpBenchmarkPrice: eaTelem.DpBenchmarkPrice, @@ -142,12 +141,17 @@ func (t *telemeter) collectV3PremiumLegacyTelemetry(d TelemetryObservation) { IsLinkFeed: false, IsNativeFeed: false, ConfigDigest: d.opts.ConfigDigest().Hex(), - Round: int64(round), - Epoch: int64(epoch), AssetSymbol: eaTelem.AssetSymbol, Version: uint32(1000 + mercuryutils.REPORT_V3), // add 1000 to distinguish between legacy feeds, this can be changed if necessary DonId: t.donID, } + epoch, round, err := evm.SeqNrToEpochAndRound(d.opts.OutCtx().SeqNr) + if err != nil { + t.eng.SugaredLogger.Warnw("Failed to convert sequence number to epoch and round", "err", err) + } else { + tea.Round = int64(round) + tea.Epoch = int64(epoch) + } bytes, err := proto.Marshal(tea) if err != nil { diff --git a/core/services/ocr/database.go b/core/services/ocr/database.go index b5f890565f1..b570c89e5a2 100644 --- a/core/services/ocr/database.go +++ b/core/services/ocr/database.go @@ -209,7 +209,7 @@ func (d *db) StorePendingTransmission(ctx context.Context, k ocrtypes.ReportTime } func (d *db) PendingTransmissionsWithConfigDigest(ctx context.Context, cd ocrtypes.ConfigDigest) (map[ocrtypes.ReportTimestamp]ocrtypes.PendingTransmission, error) { - //nolint sqlclosecheck false positive + //nolint:sqlclosecheck // false positive rows, err := d.ds.QueryContext(ctx, ` SELECT config_digest, diff --git a/core/services/ocr2/database.go b/core/services/ocr2/database.go index 919d8ff5741..11de288432f 100644 --- a/core/services/ocr2/database.go +++ b/core/services/ocr2/database.go @@ -278,7 +278,7 @@ func (d *db) PendingTransmissionsWithConfigDigest(ctx context.Context, cd ocrtyp FROM ocr2_pending_transmissions WHERE ocr2_oracle_spec_id = $1 AND config_digest = $2 ` - rows, err := d.ds.QueryxContext(ctx, stmt, d.oracleSpecID, cd) //nolint sqlclosecheck false positive + rows, err := d.ds.QueryxContext(ctx, stmt, d.oracleSpecID, cd) if err != nil { return nil, errors.Wrap(err, "PendingTransmissionsWithConfigDigest failed to query rows") } diff --git a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go index fb59c0d0783..ddae5241883 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go @@ -23,11 +23,11 @@ import ( "github.com/jmoiron/sqlx" "github.com/onsi/gomega" "github.com/pkg/errors" + "k8s.io/utils/ptr" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" - "k8s.io/utils/pointer" //nolint:staticcheck "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2/confighelper" @@ -387,7 +387,7 @@ func setupNodeCCIP( c.Feature.UICSAKeys = &trueRef c.Feature.FeedsManager = &trueRef c.OCR.Enabled = &falseRef - c.OCR.DefaultTransactionQueueDepth = pointer.Uint32(200) + c.OCR.DefaultTransactionQueueDepth = ptr.To[uint32](200) c.OCR2.Enabled = &trueRef c.Feature.LogPoller = &trueRef c.P2P.V2.Enabled = &trueRef diff --git a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go index c80b376a2af..30aaebd4e9e 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/chainlink.go @@ -22,11 +22,11 @@ import ( "github.com/jmoiron/sqlx" "github.com/onsi/gomega" "github.com/pkg/errors" + "k8s.io/utils/ptr" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.uber.org/zap" - "k8s.io/utils/pointer" //nolint:staticcheck "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2/confighelper" @@ -383,7 +383,7 @@ func setupNodeCCIP( c.Feature.UICSAKeys = &trueRef c.Feature.FeedsManager = &trueRef c.OCR.Enabled = &falseRef - c.OCR.DefaultTransactionQueueDepth = pointer.Uint32(200) + c.OCR.DefaultTransactionQueueDepth = ptr.To[uint32](200) c.OCR2.Enabled = &trueRef c.Feature.LogPoller = &trueRef c.P2P.V2.Enabled = &trueRef diff --git a/core/services/ocrcommon/adapters.go b/core/services/ocrcommon/adapters.go index 33e4971bc82..c27a276669b 100644 --- a/core/services/ocrcommon/adapters.go +++ b/core/services/ocrcommon/adapters.go @@ -112,7 +112,7 @@ func MarshalMultichainPublicKey(ost map[string]ocrtypes.OnchainPublicKey) (ocrty if length < 0 || length > math.MaxUint16 { return nil, fmt.Errorf("pubKey doesn't fit into uint16") } - if err = binary.Write(buf, binary.LittleEndian, uint16(length)); err != nil { //nolint:gosec + if err = binary.Write(buf, binary.LittleEndian, uint16(length)); err != nil { return nil, err } _, _ = buf.Write(pubKey) diff --git a/core/services/relay/evm/chain_components_test.go b/core/services/relay/evm/chain_components_test.go index 3efa50d1ec5..f8174017c22 100644 --- a/core/services/relay/evm/chain_components_test.go +++ b/core/services/relay/evm/chain_components_test.go @@ -35,8 +35,9 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" - . "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/evmtesting" //nolint common practice to import test mods with . "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + + . "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/evmtesting" //nolint:revive // dot-imports ) const commonGasLimitOnEvms = uint64(4712388) diff --git a/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go b/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go index d0aa4a21332..31122c8a5c4 100644 --- a/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go +++ b/core/services/relay/evm/chain_reader_historical_client_wrapper_test.go @@ -13,12 +13,13 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + + . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint:revive // dot-imports ) // ClientWithContractHistory makes it possible to modify client.Client CallContract so that it returns historical data. diff --git a/core/services/relay/evm/codec/codec_test.go b/core/services/relay/evm/codec/codec_test.go index 2da88abaac1..66fc45a3037 100644 --- a/core/services/relay/evm/codec/codec_test.go +++ b/core/services/relay/evm/codec/codec_test.go @@ -16,14 +16,15 @@ import ( commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/evmtesting" - looptestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" //nolint common practice to import test mods with . + looptestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/chain_reader_tester" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/codec" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + + . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint:revive // dot-imports ) const anyExtraValue = 3 @@ -288,8 +289,8 @@ func packArgs(t *testing.T, allArgs []any, oargs abi.Arguments, request *EncodeR } if request.MissingField { - args = args[1:] //nolint we know it's non-zero len - allArgs = allArgs[1:] //nolint we know it's non-zero len + args = args[1:] + allArgs = allArgs[1:] } bytes, err := args.Pack(allArgs...) diff --git a/core/services/relay/evm/evm.go b/core/services/relay/evm/evm.go index 1a4af826046..cb97155473f 100644 --- a/core/services/relay/evm/evm.go +++ b/core/services/relay/evm/evm.go @@ -87,7 +87,7 @@ func init() { } } -var _ commontypes.Relayer = &Relayer{} //nolint:staticcheck +var _ commontypes.Relayer = &Relayer{} // The current PluginProvider interface does not support an error return. This was fine up until CCIP. // CCIP is the first product to introduce the idea of incomplete implementations of a provider based on diff --git a/core/services/relay/evm/evmtesting/chain_components_interface_tester.go b/core/services/relay/evm/evmtesting/chain_components_interface_tester.go index 9655fb78457..33f2d1ff9dd 100644 --- a/core/services/relay/evm/evmtesting/chain_components_interface_tester.go +++ b/core/services/relay/evm/evmtesting/chain_components_interface_tester.go @@ -16,7 +16,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/codec" clcommontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" @@ -32,6 +31,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/types" + + . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint:revive // dot-imports ) const ( diff --git a/core/services/relay/evm/evmtesting/run_tests.go b/core/services/relay/evm/evmtesting/run_tests.go index 5f3cdbb2fd7..b6abffdcb2f 100644 --- a/core/services/relay/evm/evmtesting/run_tests.go +++ b/core/services/relay/evm/evmtesting/run_tests.go @@ -18,7 +18,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/read" - . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . + . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint:revive // dot-imports ) const ( diff --git a/core/services/relay/evm/mercury/v1/reportcodec/report_codec_test.go b/core/services/relay/evm/mercury/v1/reportcodec/report_codec_test.go index b24e69ce387..f5e2c7453e8 100644 --- a/core/services/relay/evm/mercury/v1/reportcodec/report_codec_test.go +++ b/core/services/relay/evm/mercury/v1/reportcodec/report_codec_test.go @@ -157,7 +157,7 @@ func (r *ReportCodec) ValidFromBlockNumFromReport(report ocrtypes.Report) (int64 if n > math.MaxInt64 { return 0, fmt.Errorf("ValidFromBlockNum=%d overflows max int64", n) } - return int64(n), nil //nolint:gosec // G115 + return int64(n), nil } func Test_ReportCodec_ValidFromBlockNumFromReport(t *testing.T) { diff --git a/core/services/relay/relay.go b/core/services/relay/relay.go index 913923a9b2f..0b1293c8d79 100644 --- a/core/services/relay/relay.go +++ b/core/services/relay/relay.go @@ -37,7 +37,7 @@ type ServerAdapter struct { } // NewServerAdapter returns a new ServerAdapter. -func NewServerAdapter(r types.Relayer) *ServerAdapter { //nolint:staticcheck +func NewServerAdapter(r types.Relayer) *ServerAdapter { return &ServerAdapter{Relayer: r} } diff --git a/core/services/workflows/syncer/workflow_registry.go b/core/services/workflows/syncer/workflow_registry.go index 6642679b228..b33645cdb9e 100644 --- a/core/services/workflows/syncer/workflow_registry.go +++ b/core/services/workflows/syncer/workflow_registry.go @@ -204,12 +204,14 @@ func (w *workflowRegistry) Start(_ context.Context) error { defer w.wg.Done() defer cancel() + w.lggr.Debugw("Waiting for DON...") don, err := w.workflowDonNotifier.WaitForDon(ctx) if err != nil { w.lggr.Errorf("failed to wait for don: %v", err) return } + w.lggr.Debugw("Loading initial workflows for DON", "DON", don.ID) loadWorkflowsHead, err := w.initialWorkflowsStateLoader.LoadWorkflows(ctx, don) if err != nil { w.lggr.Errorf("failed to load workflows: %v", err) @@ -333,6 +335,7 @@ func (w *workflowRegistry) syncEventsLoop(ctx context.Context, lastReadBlockNumb case <-ctx.Done(): return case <-ticker: + w.lggr.Debugw("Syncing with WorkflowRegistry") // for each event type, send a signal for it to execute a query and produce a new // batch of event logs for i := 0; i < len(w.eventTypes); i++ { diff --git a/core/store/migrate/migrate.go b/core/store/migrate/migrate.go index 7c3d3deaaf0..c8d4a0e9621 100644 --- a/core/store/migrate/migrate.go +++ b/core/store/migrate/migrate.go @@ -105,8 +105,6 @@ func ensureMigrated(ctx context.Context, db *sql.DB, p *goose.Provider, provider } // insert records for existing migrations - //nolint - sql := fmt.Sprintf(`INSERT INTO %s (version_id, is_applied) VALUES ($1, true);`, providerTableName) return sqlutil.TransactDataSource(ctx, sqlxDB, nil, func(tx sqlutil.DataSource) error { for _, name := range names { diff --git a/core/store/migrate/migrations/0036_external_job_id.go b/core/store/migrate/migrations/0036_external_job_id.go index e8012da5e78..47442124a33 100644 --- a/core/store/migrate/migrations/0036_external_job_id.go +++ b/core/store/migrate/migrations/0036_external_job_id.go @@ -31,7 +31,6 @@ const ( ` ) -// nolint func Up36(ctx context.Context, tx *sql.Tx) error { // Add the external ID column and remove type specific ones. if _, err := tx.ExecContext(ctx, up36_1); err != nil { @@ -68,7 +67,6 @@ func Up36(ctx context.Context, tx *sql.Tx) error { return nil } -// nolint func Down36(ctx context.Context, tx *sql.Tx) error { if _, err := tx.ExecContext(ctx, down36); err != nil { return err diff --git a/core/store/migrate/migrations/0054_remove_legacy_pipeline.go b/core/store/migrate/migrations/0054_remove_legacy_pipeline.go index 6d3cb20b73d..fb4c473bdb2 100644 --- a/core/store/migrate/migrations/0054_remove_legacy_pipeline.go +++ b/core/store/migrate/migrations/0054_remove_legacy_pipeline.go @@ -32,7 +32,6 @@ type queryer interface { QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row } -// nolint func Up54(ctx context.Context, tx *sql.Tx) error { if err := CheckNoLegacyJobs(ctx, tx); err != nil { return err @@ -43,7 +42,6 @@ func Up54(ctx context.Context, tx *sql.Tx) error { return nil } -// nolint func Down54(ctx context.Context, tx *sql.Tx) error { return errors.New("irreversible migration") } diff --git a/core/store/migrate/migrations/0056_multichain.go b/core/store/migrate/migrations/0056_multichain.go index c56ff0397f1..39d7ec70b2b 100644 --- a/core/store/migrate/migrations/0056_multichain.go +++ b/core/store/migrate/migrations/0056_multichain.go @@ -44,7 +44,6 @@ DROP TABLE nodes; DROP TABLE evm_chains; ` -// nolint func Up56(ctx context.Context, tx *sql.Tx) error { if _, err := tx.ExecContext(ctx, up56); err != nil { return err @@ -71,7 +70,6 @@ func Up56(ctx context.Context, tx *sql.Tx) error { return nil } -// nolint func Down56(ctx context.Context, tx *sql.Tx) error { _, err := tx.ExecContext(ctx, down56) if err != nil { diff --git a/core/store/migrate/migrations/0195_add_not_null_to_evm_chain_id_in_job_specs.go b/core/store/migrate/migrations/0195_add_not_null_to_evm_chain_id_in_job_specs.go index a2ecb50a1c9..a689cd750eb 100644 --- a/core/store/migrate/migrations/0195_add_not_null_to_evm_chain_id_in_job_specs.go +++ b/core/store/migrate/migrations/0195_add_not_null_to_evm_chain_id_in_job_specs.go @@ -33,7 +33,6 @@ const ( ` ) -// nolint func Up195(ctx context.Context, tx *sql.Tx) error { chainID, set := os.LookupEnv(env.EVMChainIDNotNullMigration0195) if set { @@ -58,7 +57,6 @@ func Up195(ctx context.Context, tx *sql.Tx) error { return errors.Wrap(err, "failed to add null constraints") } -// nolint func Down195(ctx context.Context, tx *sql.Tx) error { if _, err := tx.ExecContext(ctx, dropNullConstraintsFromSpecs); err != nil { return err diff --git a/core/utils/big_math/big_math.go b/core/utils/big_math/big_math.go index a82621b92f5..013991480ca 100644 --- a/core/utils/big_math/big_math.go +++ b/core/utils/big_math/big_math.go @@ -58,7 +58,6 @@ func Accumulate(s []*big.Int) (r *big.Int) { return } -// nolint var ( Zero = big.NewInt(0) One = big.NewInt(1) diff --git a/core/utils/files.go b/core/utils/files.go index 71b52a0ea0a..9736e1f6926 100644 --- a/core/utils/files.go +++ b/core/utils/files.go @@ -104,7 +104,6 @@ func EnsureFilepathMaxPerms(filepath string, perms os.FileMode) (err error) { // FileSize repesents a file size in bytes. type FileSize uint64 -// nolint const ( KB = 1000 MB = 1000 * KB diff --git a/core/utils/utils.go b/core/utils/utils.go index 7ac97fabb21..98c2607baff 100644 --- a/core/utils/utils.go +++ b/core/utils/utils.go @@ -595,8 +595,6 @@ func (eb *ErrorBuffer) SetCap(cap int) { } // UnwrapError returns a list of underlying errors if passed error implements joinedError or return the err in a single-element list otherwise. -// -//nolint:errorlint // error type checks will fail on wrapped errors. Disabled since we are not doing checks on error types. func UnwrapError(err error) []error { joined, ok := err.(interface{ Unwrap() []error }) if !ok { diff --git a/core/web/middleware.go b/core/web/middleware.go index 6e9378e618f..aacb912dca7 100644 --- a/core/web/middleware.go +++ b/core/web/middleware.go @@ -2,7 +2,6 @@ package web import ( "embed" - "errors" "fmt" "io/fs" "net/http" @@ -40,7 +39,7 @@ const ( // ServeFileSystem wraps a http.FileSystem with an additional file existence check type ServeFileSystem interface { http.FileSystem - Exists(prefix string, path string) bool + Exists(prefix string, path string) (bool, error) } // EmbedFileSystem implements the ServeFileSystem interface using an embed.FS @@ -60,23 +59,19 @@ func NewEmbedFileSystem(efs embed.FS, pathPrefix string) ServeFileSystem { } // Exists implements the ServeFileSystem interface. -func (e *EmbedFileSystem) Exists(prefix string, filepath string) bool { - found := false +func (e *EmbedFileSystem) Exists(prefix string, filepath string) (found bool, err error) { if p := path.Base(strings.TrimPrefix(filepath, prefix)); len(p) < len(filepath) { - //nolint:errcheck - fs.WalkDir(e.FS, ".", func(fpath string, d fs.DirEntry, err error) error { + err = fs.WalkDir(e.FS, ".", func(fpath string, d fs.DirEntry, err error) error { fileName := path.Base(fpath) if fileName == p { found = true - // Return an error so that we terminate the search early. - // Otherwise, the search will continue for the rest of the file tree. - return errors.New("file found") + return fs.SkipAll } return nil }) } - return found + return } // Open implements the http.FileSystem interface. @@ -147,7 +142,9 @@ func (f *gzipFileHandler) findBestFile(w http.ResponseWriter, r *http.Request, f ext := extensionForEncoding(posenc) fname := fpath + ext - if f.root.Exists("/", fname) { + if ok, err := f.root.Exists("/", fname); err != nil { + return nil, nil, err + } else if ok { available = append(available, posenc) } } @@ -230,7 +227,10 @@ func ServeGzippedAssets(urlPrefix string, fs ServeFileSystem, lggr logger.Logger fileserver = http.StripPrefix(urlPrefix, fileserver) } return func(c *gin.Context) { - if fs.Exists(urlPrefix, c.Request.URL.Path) { + if ok, err := fs.Exists(urlPrefix, c.Request.URL.Path); err != nil { + lggr.Errorw("Failed to search for file", "err", err) + c.AbortWithStatus(http.StatusInternalServerError) + } else if ok { fileserver.ServeHTTP(c.Writer, c.Request) c.Abort() } else { diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index c3407e0e6e7..47c1cd423da 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -1,25 +1,25 @@ package changeset import ( - "math/big" "testing" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - "github.com/smartcontractkit/chainlink/deployment" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" - commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/stretchr/testify/require" "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink/v2/core/logger" ) func Test_NewAcceptOwnershipChangeset(t *testing.T) { - e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 2, 4) + e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, &TestConfigs{}) state, err := LoadOnchainState(e.Env) require.NoError(t, err) @@ -27,39 +27,10 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { source := allChains[0] dest := allChains[1] - newAddresses := deployment.NewMemoryAddressBook() - err = deployPrerequisiteChainContracts(e.Env, newAddresses, allChains, nil) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) - - mcmConfig := commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } - out, err := commonchangeset.DeployMCMSWithTimelock(e.Env, map[uint64]commontypes.MCMSWithTimelockConfig{ - source: mcmConfig, - dest: mcmConfig, - }) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) - newAddresses = deployment.NewMemoryAddressBook() - tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) - ocrParams := make(map[uint64]CCIPOCRParams) - for _, chain := range allChains { - ocrParams[chain] = DefaultOCRParams(e.FeedChainSel, nil) + timelocks := map[uint64]*gethwrappers.RBACTimelock{ + source: state.Chains[source].Timelock, + dest: state.Chains[dest].Timelock, } - err = deployCCIPContracts(e.Env, newAddresses, NewChainsConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainsToDeploy: allChains, - TokenConfig: tokenConfig, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - OCRParams: ocrParams, - }) - require.NoError(t, err) // at this point we have the initial deploys done, now we need to transfer ownership // to the timelock contract @@ -67,10 +38,7 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { require.NoError(t, err) // compose the transfer ownership and accept ownership changesets - _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ - source: state.Chains[source].Timelock, - dest: state.Chains[dest].Timelock, - }, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewTransferOwnershipChangeset), @@ -120,8 +88,8 @@ func genTestTransferOwnershipConfig( ) return commonchangeset.TransferOwnershipConfig{ - TimelocksPerChain: timelocksPerChain, - Contracts: contracts, + OwnersPerChain: timelocksPerChain, + Contracts: contracts, } } @@ -158,10 +126,10 @@ func genTestAcceptOwnershipConfig( ) return commonchangeset.AcceptOwnershipConfig{ - TimelocksPerChain: timelocksPerChain, - ProposerMCMSes: proposerMCMses, - Contracts: contracts, - MinDelay: time.Duration(0), + OwnersPerChain: timelocksPerChain, + ProposerMCMSes: proposerMCMses, + Contracts: contracts, + MinDelay: time.Duration(0), } } diff --git a/deployment/ccip/changeset/active_candidate.go b/deployment/ccip/changeset/active_candidate.go deleted file mode 100644 index bc65cef71e7..00000000000 --- a/deployment/ccip/changeset/active_candidate.go +++ /dev/null @@ -1,135 +0,0 @@ -package changeset - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" - "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" - cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" -) - -// PromoteAllCandidatesChangeset generates a proposal to call promoteCandidate on the CCIPHome through CapReg. -// This needs to be called after SetCandidateProposal is executed. -// TODO: make it conform to the ChangeSet interface. -func PromoteAllCandidatesChangeset( - state CCIPOnChainState, - homeChainSel, newChainSel uint64, - nodes deployment.Nodes, -) (deployment.ChangesetOutput, error) { - promoteCandidateOps, err := PromoteAllCandidatesForChainOps( - state.Chains[homeChainSel].CapabilityRegistry, - state.Chains[homeChainSel].CCIPHome, - newChainSel, - nodes.NonBootstraps(), - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - var ( - timelocksPerChain = map[uint64]common.Address{ - homeChainSel: state.Chains[homeChainSel].Timelock.Address(), - } - proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - homeChainSel: state.Chains[homeChainSel].ProposerMcm, - } - ) - prop, err := proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), - Batch: promoteCandidateOps, - }}, - "promoteCandidate for commit and execution", - 0, // minDelay - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{ - *prop, - }, - }, nil -} - -// SetCandidateExecPluginProposal calls setCandidate on the CCIPHome for setting up OCR3 exec Plugin config for the new chain. -// TODO: make it conform to the ChangeSet interface. -func SetCandidatePluginChangeset( - state CCIPOnChainState, - e deployment.Environment, - nodes deployment.Nodes, - ocrSecrets deployment.OCRSecrets, - homeChainSel, feedChainSel, newChainSel uint64, - tokenConfig TokenConfig, - pluginType cctypes.PluginType, -) (deployment.ChangesetOutput, error) { - ccipOCRParams := DefaultOCRParams( - feedChainSel, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), - ) - newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( - ocrSecrets, - state.Chains[newChainSel].OffRamp, - e.Chains[newChainSel], - nodes.NonBootstraps(), - state.Chains[homeChainSel].RMNHome.Address(), - ccipOCRParams.OCRParameters, - ccipOCRParams.CommitOffChainConfig, - ccipOCRParams.ExecuteOffChainConfig, - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - execConfig, ok := newDONArgs[pluginType] - if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("missing exec plugin in ocr3Configs") - } - - setCandidateMCMSOps, err := SetCandidateOnExistingDon( - execConfig, - state.Chains[homeChainSel].CapabilityRegistry, - state.Chains[homeChainSel].CCIPHome, - newChainSel, - nodes.NonBootstraps(), - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - - var ( - timelocksPerChain = map[uint64]common.Address{ - homeChainSel: state.Chains[homeChainSel].Timelock.Address(), - } - proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - homeChainSel: state.Chains[homeChainSel].ProposerMcm, - } - ) - prop, err := proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), - Batch: setCandidateMCMSOps, - }}, - "SetCandidate for execution", - 0, // minDelay - ) - if err != nil { - return deployment.ChangesetOutput{}, err - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{ - *prop, - }, - }, nil - -} diff --git a/deployment/ccip/changeset/active_candidate_helpers.go b/deployment/ccip/changeset/active_candidate_helpers.go deleted file mode 100644 index aea488c36b2..00000000000 --- a/deployment/ccip/changeset/active_candidate_helpers.go +++ /dev/null @@ -1,142 +0,0 @@ -package changeset - -import ( - "fmt" - "math/big" - - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" - cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" -) - -// SetCandidateExecPluginOps calls setCandidate on CCIPHome contract through the UpdateDON call on CapReg contract -// This proposes to set up OCR3 config for the provided plugin for the DON -func SetCandidateOnExistingDon( - pluginConfig ccip_home.CCIPHomeOCR3Config, - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - chainSelector uint64, - nodes deployment.Nodes, -) ([]mcms.Operation, error) { - // fetch DON ID for the chain - donID, err := internal.DonIDForChain(capReg, ccipHome, chainSelector) - if err != nil { - return nil, fmt.Errorf("fetch don id for chain: %w", err) - } - fmt.Printf("donID: %d", donID) - encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack( - "setCandidate", - donID, - pluginConfig.PluginType, - pluginConfig, - [32]byte{}, - ) - if err != nil { - return nil, fmt.Errorf("pack set candidate call: %w", err) - } - - // set candidate call - updateDonTx, err := capReg.UpdateDON( - deployment.SimTransactOpts(), - donID, - nodes.PeerIDs(), - []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: internal.CCIPCapabilityID, - Config: encodedSetCandidateCall, - }, - }, - false, - nodes.DefaultF(), - ) - if err != nil { - return nil, fmt.Errorf("update don w/ exec config: %w", err) - } - - return []mcms.Operation{{ - To: capReg.Address(), - Data: updateDonTx.Data(), - Value: big.NewInt(0), - }}, nil -} - -// PromoteCandidateOp will create the MCMS Operation for `promoteCandidateAndRevokeActive` directed towards the capabilityRegistry -func PromoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, nodes deployment.Nodes) (mcms.Operation, error) { - - allConfigs, err := ccipHome.GetAllConfigs(nil, donID, pluginType) - if err != nil { - return mcms.Operation{}, err - } - - if allConfigs.CandidateConfig.ConfigDigest == [32]byte{} { - return mcms.Operation{}, fmt.Errorf("candidate digest is empty, expected nonempty") - } - fmt.Printf("commit candidate digest after setCandidate: %x\n", allConfigs.CandidateConfig.ConfigDigest) - - encodedPromotionCall, err := internal.CCIPHomeABI.Pack( - "promoteCandidateAndRevokeActive", - donID, - pluginType, - allConfigs.CandidateConfig.ConfigDigest, - allConfigs.ActiveConfig.ConfigDigest, - ) - if err != nil { - return mcms.Operation{}, fmt.Errorf("pack promotion call: %w", err) - } - - updateDonTx, err := capReg.UpdateDON( - deployment.SimTransactOpts(), - donID, - nodes.PeerIDs(), - []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: internal.CCIPCapabilityID, - Config: encodedPromotionCall, - }, - }, - false, - nodes.DefaultF(), - ) - if err != nil { - return mcms.Operation{}, fmt.Errorf("error creating updateDon op for donID(%d) and plugin type (%d): %w", donID, pluginType, err) - } - return mcms.Operation{ - To: capReg.Address(), - Data: updateDonTx.Data(), - Value: big.NewInt(0), - }, nil -} - -// PromoteAllCandidatesForChainOps promotes the candidate commit and exec configs to active by calling promoteCandidateAndRevokeActive on CCIPHome through the UpdateDON call on CapReg contract -func PromoteAllCandidatesForChainOps( - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - chainSelector uint64, - nodes deployment.Nodes, -) ([]mcms.Operation, error) { - // fetch DON ID for the chain - donID, err := internal.DonIDForChain(capReg, ccipHome, chainSelector) - if err != nil { - return nil, fmt.Errorf("fetch don id for chain: %w", err) - } - - var mcmsOps []mcms.Operation - updateCommitOp, err := PromoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPCommit), capReg, ccipHome, nodes) - if err != nil { - return nil, fmt.Errorf("promote candidate op: %w", err) - } - mcmsOps = append(mcmsOps, updateCommitOp) - - updateExecOp, err := PromoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPExec), capReg, ccipHome, nodes) - if err != nil { - return nil, fmt.Errorf("promote candidate op: %w", err) - } - mcmsOps = append(mcmsOps, updateExecOp) - - return mcmsOps, nil -} diff --git a/deployment/ccip/changeset/consts.go b/deployment/ccip/changeset/consts.go deleted file mode 100644 index 2c3d1c60e48..00000000000 --- a/deployment/ccip/changeset/consts.go +++ /dev/null @@ -1,13 +0,0 @@ -package changeset - -type TokenSymbol string - -const ( - LinkSymbol TokenSymbol = "LINK" - WethSymbol TokenSymbol = "WETH" - USDCSymbol TokenSymbol = "USDC" - USDCName string = "USD Coin" - LinkDecimals = 18 - WethDecimals = 18 - UsdcDecimals = 6 -) diff --git a/deployment/ccip/changeset/cs_active_candidate.go b/deployment/ccip/changeset/cs_active_candidate.go new file mode 100644 index 00000000000..29516b36736 --- /dev/null +++ b/deployment/ccip/changeset/cs_active_candidate.go @@ -0,0 +1,267 @@ +package changeset + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +// PromoteAllCandidatesChangeset generates a proposal to call promoteCandidate on the CCIPHome through CapReg. +// This needs to be called after SetCandidateProposal is executed. +// TODO: make it conform to the ChangeSet interface. +func PromoteAllCandidatesChangeset( + state CCIPOnChainState, + homeChainSel, newChainSel uint64, + nodes deployment.Nodes, +) (deployment.ChangesetOutput, error) { + promoteCandidateOps, err := promoteAllCandidatesForChainOps( + state.Chains[homeChainSel].CapabilityRegistry, + state.Chains[homeChainSel].CCIPHome, + newChainSel, + nodes.NonBootstraps(), + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + var ( + timelocksPerChain = map[uint64]common.Address{ + homeChainSel: state.Chains[homeChainSel].Timelock.Address(), + } + proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ + homeChainSel: state.Chains[homeChainSel].ProposerMcm, + } + ) + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + Batch: promoteCandidateOps, + }}, + "promoteCandidate for commit and execution", + 0, // minDelay + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil +} + +// SetCandidateExecPluginProposal calls setCandidate on the CCIPHome for setting up OCR3 exec Plugin config for the new chain. +// TODO: make it conform to the ChangeSet interface. +func SetCandidatePluginChangeset( + state CCIPOnChainState, + e deployment.Environment, + nodes deployment.Nodes, + ocrSecrets deployment.OCRSecrets, + homeChainSel, feedChainSel, newChainSel uint64, + tokenConfig TokenConfig, + pluginType cctypes.PluginType, +) (deployment.ChangesetOutput, error) { + ccipOCRParams := DefaultOCRParams( + feedChainSel, + tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), + nil, + ) + newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( + ocrSecrets, + state.Chains[newChainSel].OffRamp, + e.Chains[newChainSel], + nodes.NonBootstraps(), + state.Chains[homeChainSel].RMNHome.Address(), + ccipOCRParams.OCRParameters, + ccipOCRParams.CommitOffChainConfig, + ccipOCRParams.ExecuteOffChainConfig, + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + execConfig, ok := newDONArgs[pluginType] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("missing exec plugin in ocr3Configs") + } + + setCandidateMCMSOps, err := setCandidateOnExistingDon( + execConfig, + state.Chains[homeChainSel].CapabilityRegistry, + state.Chains[homeChainSel].CCIPHome, + newChainSel, + nodes.NonBootstraps(), + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + var ( + timelocksPerChain = map[uint64]common.Address{ + homeChainSel: state.Chains[homeChainSel].Timelock.Address(), + } + proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ + homeChainSel: state.Chains[homeChainSel].ProposerMcm, + } + ) + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + Batch: setCandidateMCMSOps, + }}, + "SetCandidate for execution", + 0, // minDelay + ) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil + +} + +// setCandidateOnExistingDon calls setCandidate on CCIPHome contract through the UpdateDON call on CapReg contract +// This proposes to set up OCR3 config for the provided plugin for the DON +func setCandidateOnExistingDon( + pluginConfig ccip_home.CCIPHomeOCR3Config, + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + chainSelector uint64, + nodes deployment.Nodes, +) ([]mcms.Operation, error) { + // fetch DON ID for the chain + donID, err := internal.DonIDForChain(capReg, ccipHome, chainSelector) + if err != nil { + return nil, fmt.Errorf("fetch don id for chain: %w", err) + } + fmt.Printf("donID: %d", donID) + encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack( + "setCandidate", + donID, + pluginConfig.PluginType, + pluginConfig, + [32]byte{}, + ) + if err != nil { + return nil, fmt.Errorf("pack set candidate call: %w", err) + } + + // set candidate call + updateDonTx, err := capReg.UpdateDON( + deployment.SimTransactOpts(), + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: internal.CCIPCapabilityID, + Config: encodedSetCandidateCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return nil, fmt.Errorf("update don w/ exec config: %w", err) + } + + return []mcms.Operation{{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }}, nil +} + +// promoteCandidateOp will create the MCMS Operation for `promoteCandidateAndRevokeActive` directed towards the capabilityRegistry +func promoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, nodes deployment.Nodes) (mcms.Operation, error) { + + allConfigs, err := ccipHome.GetAllConfigs(nil, donID, pluginType) + if err != nil { + return mcms.Operation{}, err + } + + if allConfigs.CandidateConfig.ConfigDigest == [32]byte{} { + return mcms.Operation{}, fmt.Errorf("candidate digest is empty, expected nonempty") + } + fmt.Printf("commit candidate digest after setCandidate: %x\n", allConfigs.CandidateConfig.ConfigDigest) + + encodedPromotionCall, err := internal.CCIPHomeABI.Pack( + "promoteCandidateAndRevokeActive", + donID, + pluginType, + allConfigs.CandidateConfig.ConfigDigest, + allConfigs.ActiveConfig.ConfigDigest, + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("pack promotion call: %w", err) + } + + updateDonTx, err := capReg.UpdateDON( + deployment.SimTransactOpts(), + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: internal.CCIPCapabilityID, + Config: encodedPromotionCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("error creating updateDon op for donID(%d) and plugin type (%d): %w", donID, pluginType, err) + } + return mcms.Operation{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }, nil +} + +// promoteAllCandidatesForChainOps promotes the candidate commit and exec configs to active by calling promoteCandidateAndRevokeActive on CCIPHome through the UpdateDON call on CapReg contract +func promoteAllCandidatesForChainOps( + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + chainSelector uint64, + nodes deployment.Nodes, +) ([]mcms.Operation, error) { + // fetch DON ID for the chain + donID, err := internal.DonIDForChain(capReg, ccipHome, chainSelector) + if err != nil { + return nil, fmt.Errorf("fetch don id for chain: %w", err) + } + + var mcmsOps []mcms.Operation + updateCommitOp, err := promoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPCommit), capReg, ccipHome, nodes) + if err != nil { + return nil, fmt.Errorf("promote candidate op: %w", err) + } + mcmsOps = append(mcmsOps, updateCommitOp) + + updateExecOp, err := promoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPExec), capReg, ccipHome, nodes) + if err != nil { + return nil, fmt.Errorf("promote candidate op: %w", err) + } + mcmsOps = append(mcmsOps, updateExecOp) + + return mcmsOps, nil +} diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/cs_active_candidate_test.go similarity index 98% rename from deployment/ccip/changeset/active_candidate_test.go rename to deployment/ccip/changeset/cs_active_candidate_test.go index 70280a33bb0..671a06b5de0 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/cs_active_candidate_test.go @@ -141,6 +141,7 @@ func TestActiveCandidate(t *testing.T) { ccipOCRParams := DefaultOCRParams( tenv.FeedChainSel, tokenConfig.GetTokenInfo(e.Logger, state.Chains[tenv.FeedChainSel].LinkToken, state.Chains[tenv.FeedChainSel].Weth9), + nil, ) ocr3ConfigMap, err := internal.BuildOCR3ConfigForCCIPHome( deployment.XXXGenerateTestOCRSecrets(), @@ -162,7 +163,7 @@ func TestActiveCandidate(t *testing.T) { tenv.HomeChainSel: state.Chains[tenv.HomeChainSel].ProposerMcm, } ) - setCommitCandidateOp, err := SetCandidateOnExistingDon( + setCommitCandidateOp, err := setCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPCommit], state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome, @@ -179,7 +180,7 @@ func TestActiveCandidate(t *testing.T) { commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) // create the op for the commit plugin as well - setExecCandidateOp, err := SetCandidateOnExistingDon( + setExecCandidateOp, err := setCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPExec], state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome, @@ -213,7 +214,7 @@ func TestActiveCandidate(t *testing.T) { oldCandidateDigest, err := state.Chains[tenv.HomeChainSel].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) require.NoError(t, err) - promoteOps, err := PromoteAllCandidatesForChainOps(state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome, tenv.FeedChainSel, nodes.NonBootstraps()) + promoteOps, err := promoteAllCandidatesForChainOps(state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome, tenv.FeedChainSel, nodes.NonBootstraps()) require.NoError(t, err) promoteProposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), diff --git a/deployment/ccip/changeset/add_chain.go b/deployment/ccip/changeset/cs_add_chain.go similarity index 65% rename from deployment/ccip/changeset/add_chain.go rename to deployment/ccip/changeset/cs_add_chain.go index d97915c4022..262d2e85e7e 100644 --- a/deployment/ccip/changeset/add_chain.go +++ b/deployment/ccip/changeset/cs_add_chain.go @@ -5,9 +5,14 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-ccip/chainconfig" + "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" @@ -69,7 +74,7 @@ func NewChainInboundChangeset( }) } - addChainOp, err := ApplyChainConfigUpdatesOp(e, state, homeChainSel, []uint64{newChainSel}) + addChainOp, err := applyChainConfigUpdatesOp(e, state, homeChainSel, []uint64{newChainSel}) if err != nil { return deployment.ChangesetOutput{}, err } @@ -119,6 +124,8 @@ func AddDonAndSetCandidateChangeset( ccipOCRParams := DefaultOCRParams( feedChainSel, tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), + // TODO: Need USDC support. + nil, ) newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( ocrSecrets, @@ -142,7 +149,7 @@ func AddDonAndSetCandidateChangeset( return deployment.ChangesetOutput{}, fmt.Errorf("missing commit plugin in ocr3Configs") } donID := latestDon.Id + 1 - addDonOp, err := NewDonWithCandidateOp( + addDonOp, err := newDonWithCandidateOp( donID, commitConfig, state.Chains[homeChainSel].CapabilityRegistry, nodes.NonBootstraps(), @@ -177,3 +184,78 @@ func AddDonAndSetCandidateChangeset( Proposals: []timelock.MCMSWithTimelockProposal{*prop}, }, nil } + +func applyChainConfigUpdatesOp( + e deployment.Environment, + state CCIPOnChainState, + homeChainSel uint64, + chains []uint64, +) (mcms.Operation, error) { + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + if err != nil { + return mcms.Operation{}, err + } + encodedExtraChainConfig, err := chainconfig.EncodeChainConfig(chainconfig.ChainConfig{ + GasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(1000), + DAGasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(0), + OptimisticConfirmations: 1, + }) + if err != nil { + return mcms.Operation{}, err + } + var chainConfigUpdates []ccip_home.CCIPHomeChainConfigArgs + for _, chainSel := range chains { + chainConfig := setupConfigInfo(chainSel, nodes.NonBootstraps().PeerIDs(), + nodes.DefaultF(), encodedExtraChainConfig) + chainConfigUpdates = append(chainConfigUpdates, chainConfig) + } + + addChain, err := state.Chains[homeChainSel].CCIPHome.ApplyChainConfigUpdates( + deployment.SimTransactOpts(), + nil, + chainConfigUpdates, + ) + if err != nil { + return mcms.Operation{}, err + } + return mcms.Operation{ + To: state.Chains[homeChainSel].CCIPHome.Address(), + Data: addChain.Data(), + Value: big.NewInt(0), + }, nil +} + +// newDonWithCandidateOp sets the candidate commit config by calling setCandidate on CCIPHome contract through the AddDON call on CapReg contract +// This should be done first before calling any other UpdateDON calls +// This proposes to set up OCR3 config for the commit plugin for the DON +func newDonWithCandidateOp( + donID uint32, + pluginConfig ccip_home.CCIPHomeOCR3Config, + capReg *capabilities_registry.CapabilitiesRegistry, + nodes deployment.Nodes, +) (mcms.Operation, error) { + encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack( + "setCandidate", + donID, + pluginConfig.PluginType, + pluginConfig, + [32]byte{}, + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("pack set candidate call: %w", err) + } + addDonTx, err := capReg.AddDON(deployment.SimTransactOpts(), nodes.PeerIDs(), []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: internal.CCIPCapabilityID, + Config: encodedSetCandidateCall, + }, + }, false, false, nodes.DefaultF()) + if err != nil { + return mcms.Operation{}, fmt.Errorf("could not generate add don tx w/ commit config: %w", err) + } + return mcms.Operation{ + To: capReg.Address(), + Data: addDonTx.Data(), + Value: big.NewInt(0), + }, nil +} diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/cs_add_chain_test.go similarity index 97% rename from deployment/ccip/changeset/add_chain_test.go rename to deployment/ccip/changeset/cs_add_chain_test.go index 39ae27f9444..96727d0e4f8 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/cs_add_chain_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" @@ -61,12 +62,15 @@ func TestAddChainInbound(t *testing.T) { newAddresses = deployment.NewMemoryAddressBook() tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + chainConfig := make(map[uint64]CCIPOCRParams) + for _, chain := range initialDeploy { + chainConfig[chain] = DefaultOCRParams(e.FeedChainSel, nil, nil) + } err = deployCCIPContracts(e.Env, newAddresses, NewChainsConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainsToDeploy: initialDeploy, - TokenConfig: tokenConfig, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainConfigByChain: chainConfig, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) diff --git a/deployment/ccip/changeset/add_lane.go b/deployment/ccip/changeset/cs_add_lane.go similarity index 87% rename from deployment/ccip/changeset/add_lane.go rename to deployment/ccip/changeset/cs_add_lane.go index 0b16611021f..0bd03b56559 100644 --- a/deployment/ccip/changeset/add_lane.go +++ b/deployment/ccip/changeset/cs_add_lane.go @@ -10,14 +10,13 @@ import ( "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" ) -var _ deployment.ChangeSet[AddLanesConfig] = AddLanesWithTestRouter +var _ deployment.ChangeSet[AddLanesConfig] = AddLanes type InitialPrices struct { LinkPrice *big.Int // USD to the power of 18 (e18) per LINK @@ -43,6 +42,7 @@ type LaneConfig struct { DestSelector uint64 InitialPricesBySource InitialPrices FeeQuoterDestChain fee_quoter.FeeQuoterDestChainConfig + TestRouter bool } type AddLanesConfig struct { @@ -65,12 +65,12 @@ func (c AddLanesConfig) Validate() error { return nil } -// AddLanesWithTestRouter adds lanes between chains using the test router. -// AddLanesWithTestRouter is run while the contracts are still owned by the deployer. +// AddLanes adds lanes between chains. +// AddLanes is run while the contracts are still owned by the deployer. // This is useful to test the initial deployment to enable lanes between chains. -// Once the testrouter is enabled, the lanes can be used to send messages between chains with testrouter. -// On successful verification with testrouter, the lanes can be enabled with the main router with different AddLane ChangeSet. -func AddLanesWithTestRouter(e deployment.Environment, cfg AddLanesConfig) (deployment.ChangesetOutput, error) { +// If the testrouter is enabled, the lanes can be used to send messages between chains with testrouter. +// On successful verification with testrouter, the lanes can be enabled with the main router with different addLane ChangeSet. +func AddLanes(e deployment.Environment, cfg AddLanesConfig) (deployment.ChangesetOutput, error) { if err := cfg.Validate(); err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("invalid AddLanesConfig: %w", err) } @@ -100,24 +100,14 @@ func addLanes(e deployment.Environment, cfg AddLanesConfig) error { } for _, laneCfg := range cfg.LaneConfigs { e.Logger.Infow("Enabling lane with test router", "from", laneCfg.SourceSelector, "to", laneCfg.DestSelector) - if err := AddLane(e, state, laneCfg, true); err != nil { + if err := addLane(e, state, laneCfg, laneCfg.TestRouter); err != nil { return err } } return nil } -func AddLaneWithDefaultPricesAndFeeQuoterConfig(e deployment.Environment, state CCIPOnChainState, from, to uint64, isTestRouter bool) error { - cfg := LaneConfig{ - SourceSelector: from, - DestSelector: to, - InitialPricesBySource: DefaultInitialPrices, - FeeQuoterDestChain: DefaultFeeQuoterDestChainConfig(), - } - return AddLane(e, state, cfg, isTestRouter) -} - -func AddLane(e deployment.Environment, state CCIPOnChainState, config LaneConfig, isTestRouter bool) error { +func addLane(e deployment.Environment, state CCIPOnChainState, config LaneConfig, isTestRouter bool) error { // TODO: Batch var fromRouter *router.Router var toRouter *router.Router diff --git a/deployment/ccip/changeset/add_lane_test.go b/deployment/ccip/changeset/cs_add_lane_test.go similarity index 99% rename from deployment/ccip/changeset/add_lane_test.go rename to deployment/ccip/changeset/cs_add_lane_test.go index dff17d8010a..bb5e678c6cb 100644 --- a/deployment/ccip/changeset/add_lane_test.go +++ b/deployment/ccip/changeset/cs_add_lane_test.go @@ -25,13 +25,14 @@ func TestAddLanesWithTestRouter(t *testing.T) { selectors := e.Env.AllChainSelectors() chain1, chain2 := selectors[0], selectors[1] - _, err = AddLanesWithTestRouter(e.Env, AddLanesConfig{ + _, err = AddLanes(e.Env, AddLanesConfig{ LaneConfigs: []LaneConfig{ { SourceSelector: chain1, DestSelector: chain2, InitialPricesBySource: DefaultInitialPrices, FeeQuoterDestChain: DefaultFeeQuoterDestChainConfig(), + TestRouter: true, }, }, }) diff --git a/deployment/ccip/changeset/deploy.go b/deployment/ccip/changeset/cs_deploy_chain.go similarity index 51% rename from deployment/ccip/changeset/deploy.go rename to deployment/ccip/changeset/cs_deploy_chain.go index 3aa654862dc..b57c00fd796 100644 --- a/deployment/ccip/changeset/deploy.go +++ b/deployment/ccip/changeset/cs_deploy_chain.go @@ -7,414 +7,61 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "golang.org/x/sync/errgroup" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" - "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/maybe_revert_message_receiver" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_rmn_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/nonce_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_proxy_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/token_admin_registry" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/weth9" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/multicall3" -) - -var ( - MockRMN deployment.ContractType = "MockRMN" - RMNRemote deployment.ContractType = "RMNRemote" - LinkToken deployment.ContractType = "LinkToken" - ARMProxy deployment.ContractType = "ARMProxy" - WETH9 deployment.ContractType = "WETH9" - Router deployment.ContractType = "Router" - CommitStore deployment.ContractType = "CommitStore" - TokenAdminRegistry deployment.ContractType = "TokenAdminRegistry" - RegistryModule deployment.ContractType = "RegistryModuleOwnerCustom" - NonceManager deployment.ContractType = "NonceManager" - FeeQuoter deployment.ContractType = "FeeQuoter" - CCIPHome deployment.ContractType = "CCIPHome" - CCIPConfig deployment.ContractType = "CCIPConfig" - RMNHome deployment.ContractType = "RMNHome" - OnRamp deployment.ContractType = "OnRamp" - OffRamp deployment.ContractType = "OffRamp" - CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" - PriceFeed deployment.ContractType = "PriceFeed" - // Note test router maps to a regular router contract. - TestRouter deployment.ContractType = "TestRouter" - Multicall3 deployment.ContractType = "Multicall3" - CCIPReceiver deployment.ContractType = "CCIPReceiver" - BurnMintToken deployment.ContractType = "BurnMintToken" - BurnMintTokenPool deployment.ContractType = "BurnMintTokenPool" - USDCToken deployment.ContractType = "USDCToken" - USDCMockTransmitter deployment.ContractType = "USDCMockTransmitter" - USDCTokenMessenger deployment.ContractType = "USDCTokenMessenger" - USDCTokenPool deployment.ContractType = "USDCTokenPool" ) -type DeployPrerequisiteContractsOpts struct { - USDCEnabledChains []uint64 - Multicall3Enabled bool -} - -type PrerequisiteOpt func(o *DeployPrerequisiteContractsOpts) - -func WithUSDCChains(chains []uint64) PrerequisiteOpt { - return func(o *DeployPrerequisiteContractsOpts) { - o.USDCEnabledChains = chains - } -} +var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts -func WithMulticall3(enabled bool) PrerequisiteOpt { - return func(o *DeployPrerequisiteContractsOpts) { - o.Multicall3Enabled = enabled - } -} - -func deployPrerequisiteChainContracts(e deployment.Environment, ab deployment.AddressBook, selectors []uint64, opts ...PrerequisiteOpt) error { - state, err := LoadOnchainState(e) +// DeployChainContracts deploys all new CCIP v1.6 or later contracts for the given chains. +// It returns the new addresses for the contracts. +// DeployChainContracts is idempotent. If there is an error, it will return the successfully deployed addresses and the error so that the caller can call the +// changeset again with the same input to retry the failed deployment. +// Caller should update the environment's address book with the returned addresses. +func DeployChainContracts(env deployment.Environment, c DeployChainContractsConfig) (deployment.ChangesetOutput, error) { + if err := c.Validate(); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid DeployChainContractsConfig: %w", err) + } + newAddresses := deployment.NewMemoryAddressBook() + err := deployChainContractsForChains(env, newAddresses, c.HomeChainSelector, c.ChainSelectors) if err != nil { - e.Logger.Errorw("Failed to load existing onchain state", "err") - return err - } - deployGrp := errgroup.Group{} - for _, sel := range selectors { - chain := e.Chains[sel] - deployGrp.Go(func() error { - err := deployPrerequisiteContracts(e, ab, state, chain, opts...) - if err != nil { - e.Logger.Errorw("Failed to deploy prerequisite contracts", "chain", sel, "err", err) - return err - } - return nil - }) - } - return deployGrp.Wait() + env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "newAddresses", newAddresses) + return deployment.ChangesetOutput{AddressBook: newAddresses}, deployment.MaybeDataErr(err) + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{}, + AddressBook: newAddresses, + JobSpecs: nil, + }, nil } -// deployPrerequisiteContracts deploys the contracts that can be ported from previous CCIP version to the new one. -// This is only required for staging and test environments where the contracts are not already deployed. -func deployPrerequisiteContracts(e deployment.Environment, ab deployment.AddressBook, state CCIPOnChainState, chain deployment.Chain, opts ...PrerequisiteOpt) error { - deployOpts := &DeployPrerequisiteContractsOpts{} - for _, opt := range opts { - if opt != nil { - opt(deployOpts) - } - } - var isUSDC bool - for _, sel := range deployOpts.USDCEnabledChains { - if sel == chain.Selector { - isUSDC = true - break - } - } - lggr := e.Logger - chainState, chainExists := state.Chains[chain.Selector] - var weth9Contract *weth9.WETH9 - var linkTokenContract *burn_mint_erc677.BurnMintERC677 - var tokenAdminReg *token_admin_registry.TokenAdminRegistry - var registryModule *registry_module_owner_custom.RegistryModuleOwnerCustom - var rmnProxy *rmn_proxy_contract.RMNProxyContract - var r *router.Router - var mc3 *multicall3.Multicall3 - if chainExists { - weth9Contract = chainState.Weth9 - linkTokenContract = chainState.LinkToken - tokenAdminReg = chainState.TokenAdminRegistry - registryModule = chainState.RegistryModule - rmnProxy = chainState.RMNProxyExisting - r = chainState.Router - mc3 = chainState.Multicall3 - } - if rmnProxy == nil { - // we want to replicate the mainnet scenario where RMNProxy is already deployed with some existing RMN - // This will need us to use two different RMNProxy contracts - // 1. RMNProxyNew with RMNRemote - ( deployed later in chain contracts) - // 2. RMNProxyExisting with mockRMN - ( deployed here, replicating the behavior of existing RMNProxy with already set RMN) - rmn, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract] { - rmnAddr, tx2, rmn, err2 := mock_rmn_contract.DeployMockRMNContract( - chain.DeployerKey, - chain.Client, - ) - return deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract]{ - rmnAddr, rmn, tx2, deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0), err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy mock RMN", "err", err) - return err - } - lggr.Infow("deployed mock RMN", "addr", rmn.Address) - rmnProxyContract, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract] { - rmnProxyAddr, tx2, rmnProxy, err2 := rmn_proxy_contract.DeployRMNProxyContract( - chain.DeployerKey, - chain.Client, - rmn.Address, - ) - return deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract]{ - rmnProxyAddr, rmnProxy, tx2, deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_0_0), err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy RMNProxyNew", "err", err) - return err - } - lggr.Infow("deployed RMNProxyNew", "addr", rmnProxyContract.Address) - rmnProxy = rmnProxyContract.Contract - } - if tokenAdminReg == nil { - tokenAdminRegistry, err := deployment.DeployContract(e.Logger, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*token_admin_registry.TokenAdminRegistry] { - tokenAdminRegistryAddr, tx2, tokenAdminRegistry, err2 := token_admin_registry.DeployTokenAdminRegistry( - chain.DeployerKey, - chain.Client) - return deployment.ContractDeploy[*token_admin_registry.TokenAdminRegistry]{ - tokenAdminRegistryAddr, tokenAdminRegistry, tx2, deployment.NewTypeAndVersion(TokenAdminRegistry, deployment.Version1_5_0), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy token admin registry", "err", err) - return err - } - e.Logger.Infow("deployed tokenAdminRegistry", "addr", tokenAdminRegistry) - tokenAdminReg = tokenAdminRegistry.Contract - } else { - e.Logger.Infow("tokenAdminRegistry already deployed", "addr", tokenAdminReg.Address) - } - if registryModule == nil { - customRegistryModule, err := deployment.DeployContract(e.Logger, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*registry_module_owner_custom.RegistryModuleOwnerCustom] { - regModAddr, tx2, regMod, err2 := registry_module_owner_custom.DeployRegistryModuleOwnerCustom( - chain.DeployerKey, - chain.Client, - tokenAdminReg.Address()) - return deployment.ContractDeploy[*registry_module_owner_custom.RegistryModuleOwnerCustom]{ - regModAddr, regMod, tx2, deployment.NewTypeAndVersion(RegistryModule, deployment.Version1_5_0), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy custom registry module", "err", err) - return err - } - e.Logger.Infow("deployed custom registry module", "addr", customRegistryModule) - registryModule = customRegistryModule.Contract - } else { - e.Logger.Infow("custom registry module already deployed", "addr", registryModule.Address) - } - isRegistryAdded, err := tokenAdminReg.IsRegistryModule(nil, registryModule.Address()) - if err != nil { - e.Logger.Errorw("Failed to check if registry module is added on token admin registry", "err", err) - return fmt.Errorf("failed to check if registry module is added on token admin registry: %w", err) - } - if !isRegistryAdded { - tx, err := tokenAdminReg.AddRegistryModule(chain.DeployerKey, registryModule.Address()) - if err != nil { - e.Logger.Errorw("Failed to assign registry module on token admin registry", "err", err) - return fmt.Errorf("failed to assign registry module on token admin registry: %w", err) - } - - _, err = chain.Confirm(tx) - if err != nil { - e.Logger.Errorw("Failed to confirm assign registry module on token admin registry", "err", err) - return fmt.Errorf("failed to confirm assign registry module on token admin registry: %w", err) - } - e.Logger.Infow("assigned registry module on token admin registry") - } - if weth9Contract == nil { - weth, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*weth9.WETH9] { - weth9Addr, tx2, weth9c, err2 := weth9.DeployWETH9( - chain.DeployerKey, - chain.Client, - ) - return deployment.ContractDeploy[*weth9.WETH9]{ - weth9Addr, weth9c, tx2, deployment.NewTypeAndVersion(WETH9, deployment.Version1_0_0), err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy weth9", "err", err) - return err - } - lggr.Infow("deployed weth9", "addr", weth.Address) - weth9Contract = weth.Contract - } else { - lggr.Infow("weth9 already deployed", "addr", weth9Contract.Address) - } - if linkTokenContract == nil { - linkToken, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677] { - linkTokenAddr, tx2, linkToken, err2 := burn_mint_erc677.DeployBurnMintERC677( - chain.DeployerKey, - chain.Client, - "Link Token", - "LINK", - uint8(18), - big.NewInt(0).Mul(big.NewInt(1e9), big.NewInt(1e18)), - ) - return deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677]{ - linkTokenAddr, linkToken, tx2, deployment.NewTypeAndVersion(LinkToken, deployment.Version1_0_0), err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy linkToken", "err", err) - return err - } - lggr.Infow("deployed linkToken", "addr", linkToken.Address) - } else { - lggr.Infow("linkToken already deployed", "addr", linkTokenContract.Address) - } - // if router is not already deployed, we deploy it - if r == nil { - routerContract, err := deployment.DeployContract(e.Logger, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*router.Router] { - routerAddr, tx2, routerC, err2 := router.DeployRouter( - chain.DeployerKey, - chain.Client, - weth9Contract.Address(), - rmnProxy.Address(), - ) - return deployment.ContractDeploy[*router.Router]{ - routerAddr, routerC, tx2, deployment.NewTypeAndVersion(Router, deployment.Version1_2_0), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy router", "err", err) - return err - } - e.Logger.Infow("deployed router", "addr", routerContract.Address) - r = routerContract.Contract - } else { - e.Logger.Infow("router already deployed", "addr", chainState.Router.Address) - } - if deployOpts.Multicall3Enabled && mc3 == nil { - multicall3Contract, err := deployment.DeployContract(e.Logger, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*multicall3.Multicall3] { - multicall3Addr, tx2, multicall3Wrapper, err2 := multicall3.DeployMulticall3( - chain.DeployerKey, - chain.Client, - ) - return deployment.ContractDeploy[*multicall3.Multicall3]{ - multicall3Addr, multicall3Wrapper, tx2, deployment.NewTypeAndVersion(Multicall3, deployment.Version1_0_0), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy ccip multicall", "err", err) - return err - } - e.Logger.Infow("deployed ccip multicall", "addr", multicall3Contract.Address) - } else { - e.Logger.Info("ccip multicall already deployed", "addr", mc3.Address) - } - if isUSDC { - token, pool, messenger, transmitter, err1 := DeployUSDC(e.Logger, chain, ab, rmnProxy.Address(), r.Address()) - if err1 != nil { - return err1 - } - e.Logger.Infow("Deployed USDC contracts", - "chainSelector", chain.Selector, - "token", token.Address(), - "pool", pool.Address(), - "transmitter", transmitter.Address(), - "messenger", messenger.Address(), - ) - } - return nil +type DeployChainContractsConfig struct { + ChainSelectors []uint64 + HomeChainSelector uint64 } -// configureChain assumes the all the Home chain contracts and CCIP contracts are deployed -// It does - -// 1. AddChainConfig for each chain in CCIPHome -// 2. Registers the nodes with the capability registry -// 3. SetOCR3Config on the remote chain -func configureChain( - e deployment.Environment, - c NewChainsConfig, -) error { - if c.OCRSecrets.IsEmpty() { - return fmt.Errorf("OCR secrets are empty") - } - nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) - if err != nil || len(nodes) == 0 { - e.Logger.Errorw("Failed to get node info", "err", err) - return err - } - existingState, err := LoadOnchainState(e) - if err != nil { - e.Logger.Errorw("Failed to load existing onchain state", "err") - return err - } - capReg := existingState.Chains[c.HomeChainSel].CapabilityRegistry - if capReg == nil { - e.Logger.Errorw("Failed to get capability registry") - return fmt.Errorf("capability registry not found") - } - ccipHome := existingState.Chains[c.HomeChainSel].CCIPHome - if ccipHome == nil { - e.Logger.Errorw("Failed to get ccip home", "err", err) - return fmt.Errorf("ccip home not found") - } - rmnHome := existingState.Chains[c.HomeChainSel].RMNHome - if rmnHome == nil { - e.Logger.Errorw("Failed to get rmn home", "err", err) - return fmt.Errorf("rmn home not found") - } - - for _, chainSel := range c.ChainsToDeploy { - chain, _ := e.Chains[chainSel] - chainState, ok := existingState.Chains[chain.Selector] - if !ok { - return fmt.Errorf("chain state not found for chain %d", chain.Selector) - } - ocrParams, ok := c.OCRParams[chain.Selector] - if !ok { - return fmt.Errorf("OCR params not found for chain %d", chain.Selector) - } - if chainState.OffRamp == nil { - return fmt.Errorf("off ramp not found for chain %d", chain.Selector) - } - _, err = AddChainConfig( - e.Logger, - e.Chains[c.HomeChainSel], - ccipHome, - chain.Selector, - nodes.NonBootstraps().PeerIDs()) - if err != nil { - return err - } - if enabled, ok := c.USDCConfig.EnabledChainMap()[chainSel]; ok && enabled { - ocrParams.ExecuteOffChainConfig.TokenDataObservers = c.USDCConfig.ToTokenDataObserverConfig() - } - ocrParams.CommitOffChainConfig.PriceFeedChainSelector = cciptypes.ChainSelector(c.FeedChainSel) - // For each chain, we create a DON on the home chain (2 OCR instances) - if err := addDON( - e.Logger, - c.OCRSecrets, - capReg, - ccipHome, - rmnHome.Address(), - chainState.OffRamp, - chain, - e.Chains[c.HomeChainSel], - nodes.NonBootstraps(), - ocrParams, - ); err != nil { - e.Logger.Errorw("Failed to add DON", "err", err) - return err +func (c DeployChainContractsConfig) Validate() error { + for _, cs := range c.ChainSelectors { + if err := deployment.IsValidChainSelector(cs); err != nil { + return fmt.Errorf("invalid chain selector: %d - %w", cs, err) } } - + if err := deployment.IsValidChainSelector(c.HomeChainSelector); err != nil { + return fmt.Errorf("invalid home chain selector: %d - %w", c.HomeChainSelector, err) + } return nil } @@ -431,7 +78,7 @@ func deployCCIPContracts( e deployment.Environment, ab deployment.AddressBook, c NewChainsConfig) error { - err := deployChainContractsForChains(e, ab, c.HomeChainSel, c.ChainsToDeploy) + err := deployChainContractsForChains(e, ab, c.HomeChainSel, c.Chains()) if err != nil { e.Logger.Errorw("Failed to deploy chain contracts", "err", err) return err @@ -441,18 +88,6 @@ func deployCCIPContracts( e.Logger.Errorw("Failed to merge address book", "err", err) return err } - state, err := LoadOnchainState(e) - if err != nil { - e.Logger.Errorw("Failed to load existing onchain state", "err", err) - return err - } - - ocrParams := make(map[uint64]CCIPOCRParams) - for _, chain := range c.ChainsToDeploy { - tokenInfo := c.TokenConfig.GetTokenInfo(e.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - ocrParams[chain] = DefaultOCRParams(c.FeedChainSel, tokenInfo) - } - c.OCRParams = ocrParams err = configureChain(e, c) if err != nil { e.Logger.Errorw("Failed to add chain", "err", err) @@ -599,6 +234,8 @@ func deployChainContracts( chain.DeployerKey, chain.Client, chain.Selector, + // Indicates no legacy RMN contract + common.HexToAddress("0x0"), ) return deployment.ContractDeploy[*rmn_remote.RMNRemote]{ rmnRemoteAddr, rmnRemote, tx, deployment.NewTypeAndVersion(RMNRemote, deployment.Version1_6_0_dev), err2, diff --git a/deployment/ccip/changeset/deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go similarity index 85% rename from deployment/ccip/changeset/deploy_chain_test.go rename to deployment/ccip/changeset/cs_deploy_chain_test.go index acab6fde6cb..f599ab2d6f3 100644 --- a/deployment/ccip/changeset/deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -1,6 +1,8 @@ package changeset import ( + "encoding/json" + "fmt" "math/big" "testing" @@ -91,3 +93,19 @@ func TestDeployChainContractsChangeset(t *testing.T) { require.NotNil(t, state.Chains[sel].OnRamp) } } + +func TestDeployCCIPContracts(t *testing.T) { + lggr := logger.TestLogger(t) + e := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 2, 4, nil) + // Deploy all the CCIP contracts. + state, err := LoadOnchainState(e.Env) + require.NoError(t, err) + snap, err := state.View(e.Env.AllChainSelectors()) + require.NoError(t, err) + + // Assert expect every deployed address to be in the address book. + // TODO (CCIP-3047): Add the rest of CCIPv2 representation + b, err := json.MarshalIndent(snap, "", " ") + require.NoError(t, err) + fmt.Println(string(b)) +} diff --git a/deployment/ccip/changeset/cs_home_chain.go b/deployment/ccip/changeset/cs_home_chain.go new file mode 100644 index 00000000000..0df8d87affb --- /dev/null +++ b/deployment/ccip/changeset/cs_home_chain.go @@ -0,0 +1,325 @@ +package changeset + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" +) + +var _ deployment.ChangeSet[DeployHomeChainConfig] = DeployHomeChain + +// DeployHomeChain is a separate changeset because it is a standalone deployment performed once in home chain for the entire CCIP deployment. +func DeployHomeChain(env deployment.Environment, cfg DeployHomeChainConfig) (deployment.ChangesetOutput, error) { + err := cfg.Validate() + if err != nil { + return deployment.ChangesetOutput{}, errors.Wrapf(deployment.ErrInvalidConfig, "%v", err) + } + ab := deployment.NewMemoryAddressBook() + // Note we also deploy the cap reg. + _, err = deployHomeChain(env.Logger, env, ab, env.Chains[cfg.HomeChainSel], cfg.RMNStaticConfig, cfg.RMNDynamicConfig, cfg.NodeOperators, cfg.NodeP2PIDsPerNodeOpAdmin) + if err != nil { + env.Logger.Errorw("Failed to deploy cap reg", "err", err, "addresses", env.ExistingAddresses) + return deployment.ChangesetOutput{ + AddressBook: ab, + }, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{}, + AddressBook: ab, + JobSpecs: nil, + }, nil +} + +type DeployHomeChainConfig struct { + HomeChainSel uint64 + RMNStaticConfig rmn_home.RMNHomeStaticConfig + RMNDynamicConfig rmn_home.RMNHomeDynamicConfig + NodeOperators []capabilities_registry.CapabilitiesRegistryNodeOperator + NodeP2PIDsPerNodeOpAdmin map[string][][32]byte +} + +func (c DeployHomeChainConfig) Validate() error { + if c.HomeChainSel == 0 { + return fmt.Errorf("home chain selector must be set") + } + if c.RMNDynamicConfig.OffchainConfig == nil { + return fmt.Errorf("offchain config for RMNHomeDynamicConfig must be set") + } + if c.RMNStaticConfig.OffchainConfig == nil { + return fmt.Errorf("offchain config for RMNHomeStaticConfig must be set") + } + if len(c.NodeOperators) == 0 { + return fmt.Errorf("node operators must be set") + } + for _, nop := range c.NodeOperators { + if nop.Admin == (common.Address{}) { + return fmt.Errorf("node operator admin address must be set") + } + if nop.Name == "" { + return fmt.Errorf("node operator name must be set") + } + if len(c.NodeP2PIDsPerNodeOpAdmin[nop.Name]) == 0 { + return fmt.Errorf("node operator %s must have node p2p ids provided", nop.Name) + } + } + + return nil +} + +// deployCapReg deploys the CapabilitiesRegistry contract if it is not already deployed +// and returns a deployment.ContractDeploy struct with the address and contract instance. +func deployCapReg( + lggr logger.Logger, + state CCIPOnChainState, + ab deployment.AddressBook, + chain deployment.Chain, +) (*deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) { + homeChainState, exists := state.Chains[chain.Selector] + if exists { + cr := homeChainState.CapabilityRegistry + if cr != nil { + lggr.Infow("Found CapabilitiesRegistry in chain state", "address", cr.Address().String()) + return &deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry]{ + Address: cr.Address(), Contract: cr, Tv: deployment.NewTypeAndVersion(CapabilitiesRegistry, deployment.Version1_0_0), + }, nil + } + } + capReg, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry] { + crAddr, tx, cr, err2 := capabilities_registry.DeployCapabilitiesRegistry( + chain.DeployerKey, + chain.Client, + ) + return deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry]{ + Address: crAddr, Contract: cr, Tv: deployment.NewTypeAndVersion(CapabilitiesRegistry, deployment.Version1_0_0), Tx: tx, Err: err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy capreg", "err", err) + return nil, err + } + return capReg, nil +} + +func deployHomeChain( + lggr logger.Logger, + e deployment.Environment, + ab deployment.AddressBook, + chain deployment.Chain, + rmnHomeStatic rmn_home.RMNHomeStaticConfig, + rmnHomeDynamic rmn_home.RMNHomeDynamicConfig, + nodeOps []capabilities_registry.CapabilitiesRegistryNodeOperator, + nodeP2PIDsPerNodeOpAdmin map[string][][32]byte, +) (*deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) { + // load existing state + state, err := LoadOnchainState(e) + if err != nil { + return nil, fmt.Errorf("failed to load onchain state: %w", err) + } + // Deploy CapabilitiesRegistry, CCIPHome, RMNHome + capReg, err := deployCapReg(lggr, state, ab, chain) + if err != nil { + return nil, err + } + + lggr.Infow("deployed/connected to capreg", "addr", capReg.Address) + ccipHome, err := deployment.DeployContract( + lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*ccip_home.CCIPHome] { + ccAddr, tx, cc, err2 := ccip_home.DeployCCIPHome( + chain.DeployerKey, + chain.Client, + capReg.Address, + ) + return deployment.ContractDeploy[*ccip_home.CCIPHome]{ + Address: ccAddr, Tv: deployment.NewTypeAndVersion(CCIPHome, deployment.Version1_6_0_dev), Tx: tx, Err: err2, Contract: cc, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy CCIPHome", "err", err) + return nil, err + } + lggr.Infow("deployed CCIPHome", "addr", ccipHome.Address) + + rmnHome, err := deployment.DeployContract( + lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*rmn_home.RMNHome] { + rmnAddr, tx, rmn, err2 := rmn_home.DeployRMNHome( + chain.DeployerKey, + chain.Client, + ) + return deployment.ContractDeploy[*rmn_home.RMNHome]{ + Address: rmnAddr, Tv: deployment.NewTypeAndVersion(RMNHome, deployment.Version1_6_0_dev), Tx: tx, Err: err2, Contract: rmn, + } + }, + ) + if err != nil { + lggr.Errorw("Failed to deploy RMNHome", "err", err) + return nil, err + } + lggr.Infow("deployed RMNHome", "addr", rmnHome.Address) + + // considering the RMNHome is recently deployed, there is no digest to overwrite + tx, err := rmnHome.Contract.SetCandidate(chain.DeployerKey, rmnHomeStatic, rmnHomeDynamic, [32]byte{}) + if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { + lggr.Errorw("Failed to set candidate on RMNHome", "err", err) + return nil, err + } + + rmnCandidateDigest, err := rmnHome.Contract.GetCandidateDigest(nil) + if err != nil { + lggr.Errorw("Failed to get RMNHome candidate digest", "err", err) + return nil, err + } + + tx, err = rmnHome.Contract.PromoteCandidateAndRevokeActive(chain.DeployerKey, rmnCandidateDigest, [32]byte{}) + if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { + lggr.Errorw("Failed to promote candidate and revoke active on RMNHome", "err", err) + return nil, err + } + + rmnActiveDigest, err := rmnHome.Contract.GetActiveDigest(nil) + if err != nil { + lggr.Errorw("Failed to get RMNHome active digest", "err", err) + return nil, err + } + lggr.Infow("Got rmn home active digest", "digest", rmnActiveDigest) + + if rmnActiveDigest != rmnCandidateDigest { + lggr.Errorw("RMNHome active digest does not match previously candidate digest", + "active", rmnActiveDigest, "candidate", rmnCandidateDigest) + return nil, errors.New("RMNHome active digest does not match candidate digest") + } + + tx, err = capReg.Contract.AddCapabilities(chain.DeployerKey, []capabilities_registry.CapabilitiesRegistryCapability{ + { + LabelledName: internal.CapabilityLabelledName, + Version: internal.CapabilityVersion, + CapabilityType: 2, // consensus. not used (?) + ResponseType: 0, // report. not used (?) + ConfigurationContract: ccipHome.Address, + }, + }) + if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { + lggr.Errorw("Failed to add capabilities", "err", err) + return nil, err + } + + tx, err = capReg.Contract.AddNodeOperators(chain.DeployerKey, nodeOps) + txBlockNum, err := deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + lggr.Errorw("Failed to add node operators", "err", err) + return nil, err + } + addedEvent, err := capReg.Contract.FilterNodeOperatorAdded(&bind.FilterOpts{ + Start: txBlockNum, + Context: context.Background(), + }, nil, nil) + if err != nil { + lggr.Errorw("Failed to filter NodeOperatorAdded event", "err", err) + return capReg, err + } + // Need to fetch nodeoperators ids to be able to add nodes for corresponding node operators + p2pIDsByNodeOpId := make(map[uint32][][32]byte) + for addedEvent.Next() { + for nopName, p2pId := range nodeP2PIDsPerNodeOpAdmin { + if addedEvent.Event.Name == nopName { + lggr.Infow("Added node operator", "admin", addedEvent.Event.Admin, "name", addedEvent.Event.Name) + p2pIDsByNodeOpId[addedEvent.Event.NodeOperatorId] = p2pId + } + } + } + if len(p2pIDsByNodeOpId) != len(nodeP2PIDsPerNodeOpAdmin) { + lggr.Errorw("Failed to add all node operators", "added", maps.Keys(p2pIDsByNodeOpId), "expected", maps.Keys(nodeP2PIDsPerNodeOpAdmin)) + return capReg, errors.New("failed to add all node operators") + } + // Adds initial set of nodes to CR, who all have the CCIP capability + if err := addNodes(lggr, capReg.Contract, chain, p2pIDsByNodeOpId); err != nil { + return capReg, err + } + return capReg, nil +} + +func isEqualCapabilitiesRegistryNodeParams(a, b capabilities_registry.CapabilitiesRegistryNodeParams) (bool, error) { + aBytes, err := json.Marshal(a) + if err != nil { + return false, err + } + bBytes, err := json.Marshal(b) + if err != nil { + return false, err + } + return bytes.Equal(aBytes, bBytes), nil +} + +func addNodes( + lggr logger.Logger, + capReg *capabilities_registry.CapabilitiesRegistry, + chain deployment.Chain, + p2pIDsByNodeOpId map[uint32][][32]byte, +) error { + var nodeParams []capabilities_registry.CapabilitiesRegistryNodeParams + nodes, err := capReg.GetNodes(nil) + if err != nil { + return err + } + existingNodeParams := make(map[p2ptypes.PeerID]capabilities_registry.CapabilitiesRegistryNodeParams) + for _, node := range nodes { + existingNodeParams[node.P2pId] = capabilities_registry.CapabilitiesRegistryNodeParams{ + NodeOperatorId: node.NodeOperatorId, + Signer: node.Signer, + P2pId: node.P2pId, + HashedCapabilityIds: node.HashedCapabilityIds, + } + } + for nopID, p2pIDs := range p2pIDsByNodeOpId { + for _, p2pID := range p2pIDs { + // if any p2pIDs are empty throw error + if bytes.Equal(p2pID[:], make([]byte, 32)) { + return errors.Wrapf(errors.New("empty p2pID"), "p2pID: %x selector: %d", p2pID, chain.Selector) + } + nodeParam := capabilities_registry.CapabilitiesRegistryNodeParams{ + NodeOperatorId: nopID, + Signer: p2pID, // Not used in tests + P2pId: p2pID, + EncryptionPublicKey: p2pID, // Not used in tests + HashedCapabilityIds: [][32]byte{internal.CCIPCapabilityID}, + } + if existing, ok := existingNodeParams[p2pID]; ok { + if isEqual, err := isEqualCapabilitiesRegistryNodeParams(existing, nodeParam); err != nil && isEqual { + lggr.Infow("Node already exists", "p2pID", p2pID) + continue + } + } + + nodeParams = append(nodeParams, nodeParam) + } + } + if len(nodeParams) == 0 { + lggr.Infow("No new nodes to add") + return nil + } + tx, err := capReg.AddNodes(chain.DeployerKey, nodeParams) + if err != nil { + lggr.Errorw("Failed to add nodes", "err", deployment.MaybeDataErr(err)) + return err + } + _, err = chain.Confirm(tx) + return err +} diff --git a/deployment/ccip/changeset/home_chain_test.go b/deployment/ccip/changeset/cs_home_chain_test.go similarity index 100% rename from deployment/ccip/changeset/home_chain_test.go rename to deployment/ccip/changeset/cs_home_chain_test.go diff --git a/deployment/ccip/changeset/cs_initial_add_chain.go b/deployment/ccip/changeset/cs_initial_add_chain.go new file mode 100644 index 00000000000..0e425aef8c7 --- /dev/null +++ b/deployment/ccip/changeset/cs_initial_add_chain.go @@ -0,0 +1,449 @@ +package changeset + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "os" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + + "github.com/smartcontractkit/chainlink-ccip/chainconfig" + "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/types" + cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" +) + +var _ deployment.ChangeSet[NewChainsConfig] = ConfigureNewChains + +// ConfigureNewChains enables new chains as destination(s) for CCIP +// It performs the following steps per chain: +// - addChainConfig + AddDON (candidate->primary promotion i.e. init) on the home chain +// - SetOCR3Config on the remote chain +// ConfigureNewChains assumes that the home chain is already enabled and all CCIP contracts are already deployed. +func ConfigureNewChains(env deployment.Environment, c NewChainsConfig) (deployment.ChangesetOutput, error) { + if err := c.Validate(); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid NewChainsConfig: %w", err) + } + err := configureChain(env, c) + if err != nil { + env.Logger.Errorw("Failed to configure chain", "err", err) + return deployment.ChangesetOutput{}, deployment.MaybeDataErr(err) + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{}, + AddressBook: nil, + JobSpecs: nil, + }, nil +} + +type CCIPOCRParams struct { + OCRParameters types.OCRParameters + // Note contains pointers to Arb feeds for prices + CommitOffChainConfig pluginconfig.CommitOffchainConfig + // Note ontains USDC config + ExecuteOffChainConfig pluginconfig.ExecuteOffchainConfig +} + +func (c CCIPOCRParams) Validate() error { + if err := c.OCRParameters.Validate(); err != nil { + return fmt.Errorf("invalid OCR parameters: %w", err) + } + if err := c.CommitOffChainConfig.Validate(); err != nil { + return fmt.Errorf("invalid commit off-chain config: %w", err) + } + if err := c.ExecuteOffChainConfig.Validate(); err != nil { + return fmt.Errorf("invalid execute off-chain config: %w", err) + } + return nil +} + +type NewChainsConfig struct { + // Common to all chains + HomeChainSel uint64 + FeedChainSel uint64 + OCRSecrets deployment.OCRSecrets + // Per chain config + ChainConfigByChain map[uint64]CCIPOCRParams +} + +func (c NewChainsConfig) Chains() []uint64 { + chains := make([]uint64, 0, len(c.ChainConfigByChain)) + for chain := range c.ChainConfigByChain { + chains = append(chains, chain) + } + return chains +} + +func (c NewChainsConfig) Validate() error { + if err := deployment.IsValidChainSelector(c.HomeChainSel); err != nil { + return fmt.Errorf("invalid home chain selector: %d - %w", c.HomeChainSel, err) + } + if err := deployment.IsValidChainSelector(c.FeedChainSel); err != nil { + return fmt.Errorf("invalid feed chain selector: %d - %w", c.FeedChainSel, err) + } + if c.OCRSecrets.IsEmpty() { + return fmt.Errorf("no OCR secrets provided") + } + // Validate chain config + for chain, cfg := range c.ChainConfigByChain { + if err := cfg.Validate(); err != nil { + return fmt.Errorf("invalid OCR params for chain %d: %w", chain, err) + } + if cfg.CommitOffChainConfig.PriceFeedChainSelector != ccipocr3.ChainSelector(c.FeedChainSel) { + return fmt.Errorf("chain %d has invalid feed chain selector", chain) + } + } + return nil +} + +// DefaultOCRParams returns the default OCR parameters for a chain, +// except for a few values which must be parameterized (passed as arguments). +func DefaultOCRParams( + feedChainSel uint64, + tokenInfo map[ccipocr3.UnknownEncodedAddress]pluginconfig.TokenInfo, + tokenDataObservers []pluginconfig.TokenDataObserverConfig, +) CCIPOCRParams { + return CCIPOCRParams{ + OCRParameters: types.OCRParameters{ + DeltaProgress: internal.DeltaProgress, + DeltaResend: internal.DeltaResend, + DeltaInitial: internal.DeltaInitial, + DeltaRound: internal.DeltaRound, + DeltaGrace: internal.DeltaGrace, + DeltaCertifiedCommitRequest: internal.DeltaCertifiedCommitRequest, + DeltaStage: internal.DeltaStage, + Rmax: internal.Rmax, + MaxDurationQuery: internal.MaxDurationQuery, + MaxDurationObservation: internal.MaxDurationObservation, + MaxDurationShouldAcceptAttestedReport: internal.MaxDurationShouldAcceptAttestedReport, + MaxDurationShouldTransmitAcceptedReport: internal.MaxDurationShouldTransmitAcceptedReport, + }, + ExecuteOffChainConfig: pluginconfig.ExecuteOffchainConfig{ + BatchGasLimit: internal.BatchGasLimit, + RelativeBoostPerWaitHour: internal.RelativeBoostPerWaitHour, + InflightCacheExpiry: *config.MustNewDuration(internal.InflightCacheExpiry), + RootSnoozeTime: *config.MustNewDuration(internal.RootSnoozeTime), + MessageVisibilityInterval: *config.MustNewDuration(internal.FirstBlockAge), + BatchingStrategyID: internal.BatchingStrategyID, + TokenDataObservers: tokenDataObservers, + }, + CommitOffChainConfig: pluginconfig.CommitOffchainConfig{ + RemoteGasPriceBatchWriteFrequency: *config.MustNewDuration(internal.RemoteGasPriceBatchWriteFrequency), + TokenPriceBatchWriteFrequency: *config.MustNewDuration(internal.TokenPriceBatchWriteFrequency), + TokenInfo: tokenInfo, + PriceFeedChainSelector: ccipocr3.ChainSelector(feedChainSel), + NewMsgScanBatchSize: merklemulti.MaxNumberTreeLeaves, + MaxReportTransmissionCheckAttempts: 5, + RMNEnabled: os.Getenv("ENABLE_RMN") == "true", // only enabled in manual test + RMNSignaturesTimeout: 30 * time.Minute, + MaxMerkleTreeSize: merklemulti.MaxNumberTreeLeaves, + SignObservationPrefix: "chainlink ccip 1.6 rmn observation", + }, + } +} + +// configureChain assumes the all the Home chain contracts and CCIP contracts are deployed +// It does - +// 1. addChainConfig for each chain in CCIPHome +// 2. Registers the nodes with the capability registry +// 3. SetOCR3Config on the remote chain +func configureChain( + e deployment.Environment, + c NewChainsConfig, +) error { + if c.OCRSecrets.IsEmpty() { + return fmt.Errorf("OCR secrets are empty") + } + nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) + if err != nil || len(nodes) == 0 { + e.Logger.Errorw("Failed to get node info", "err", err) + return err + } + existingState, err := LoadOnchainState(e) + if err != nil { + e.Logger.Errorw("Failed to load existing onchain state", "err") + return err + } + capReg := existingState.Chains[c.HomeChainSel].CapabilityRegistry + if capReg == nil { + e.Logger.Errorw("Failed to get capability registry") + return fmt.Errorf("capability registry not found") + } + ccipHome := existingState.Chains[c.HomeChainSel].CCIPHome + if ccipHome == nil { + e.Logger.Errorw("Failed to get ccip home", "err", err) + return fmt.Errorf("ccip home not found") + } + rmnHome := existingState.Chains[c.HomeChainSel].RMNHome + if rmnHome == nil { + e.Logger.Errorw("Failed to get rmn home", "err", err) + return fmt.Errorf("rmn home not found") + } + + for chainSel, chainConfig := range c.ChainConfigByChain { + chain, _ := e.Chains[chainSel] + chainState, ok := existingState.Chains[chain.Selector] + if !ok { + return fmt.Errorf("chain state not found for chain %d", chain.Selector) + } + if chainState.OffRamp == nil { + return fmt.Errorf("off ramp not found for chain %d", chain.Selector) + } + _, err = addChainConfig( + e.Logger, + e.Chains[c.HomeChainSel], + ccipHome, + chain.Selector, + nodes.NonBootstraps().PeerIDs()) + if err != nil { + return err + } + // For each chain, we create a DON on the home chain (2 OCR instances) + if err := addDON( + e.Logger, + c.OCRSecrets, + capReg, + ccipHome, + rmnHome.Address(), + chainState.OffRamp, + chain, + e.Chains[c.HomeChainSel], + nodes.NonBootstraps(), + chainConfig, + ); err != nil { + e.Logger.Errorw("Failed to add DON", "err", err) + return err + } + } + + return nil +} + +func setupConfigInfo(chainSelector uint64, readers [][32]byte, fChain uint8, cfg []byte) ccip_home.CCIPHomeChainConfigArgs { + return ccip_home.CCIPHomeChainConfigArgs{ + ChainSelector: chainSelector, + ChainConfig: ccip_home.CCIPHomeChainConfig{ + Readers: readers, + FChain: fChain, + Config: cfg, + }, + } +} + +func addChainConfig( + lggr logger.Logger, + h deployment.Chain, + ccipConfig *ccip_home.CCIPHome, + chainSelector uint64, + p2pIDs [][32]byte, +) (ccip_home.CCIPHomeChainConfigArgs, error) { + // First Add CCIPOCRParams that includes all p2pIDs as readers + encodedExtraChainConfig, err := chainconfig.EncodeChainConfig(chainconfig.ChainConfig{ + GasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(1000), + DAGasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(0), + OptimisticConfirmations: 1, + }) + if err != nil { + return ccip_home.CCIPHomeChainConfigArgs{}, err + } + chainConfig := setupConfigInfo(chainSelector, p2pIDs, uint8(len(p2pIDs)/3), encodedExtraChainConfig) + tx, err := ccipConfig.ApplyChainConfigUpdates(h.DeployerKey, nil, []ccip_home.CCIPHomeChainConfigArgs{ + chainConfig, + }) + if _, err := deployment.ConfirmIfNoError(h, tx, err); err != nil { + return ccip_home.CCIPHomeChainConfigArgs{}, err + } + lggr.Infow("Applied chain config updates", "chainConfig", chainConfig) + return chainConfig, nil +} + +// createDON creates one DON with 2 plugins (commit and exec) +// It first set a new candidate for the DON with the first plugin type and AddDON on capReg +// Then for subsequent operations it uses UpdateDON to promote the first plugin to the active deployment +// and to set candidate and promote it for the second plugin +func createDON( + lggr logger.Logger, + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + ocr3Configs map[cctypes.PluginType]ccip_home.CCIPHomeOCR3Config, + home deployment.Chain, + newChainSel uint64, + nodes deployment.Nodes, +) error { + commitConfig, ok := ocr3Configs[cctypes.PluginTypeCCIPCommit] + if !ok { + return fmt.Errorf("missing commit plugin in ocr3Configs") + } + + execConfig, ok := ocr3Configs[cctypes.PluginTypeCCIPExec] + if !ok { + return fmt.Errorf("missing exec plugin in ocr3Configs") + } + + latestDon, err := internal.LatestCCIPDON(capReg) + if err != nil { + return err + } + + donID := latestDon.Id + 1 + + err = internal.SetupCommitDON(donID, commitConfig, capReg, home, nodes, ccipHome) + if err != nil { + return fmt.Errorf("setup commit don: %w", err) + } + + // TODO: bug in contract causing this to not work as expected. + err = internal.SetupExecDON(donID, execConfig, capReg, home, nodes, ccipHome) + if err != nil { + return fmt.Errorf("setup exec don: %w", err) + } + return ValidateCCIPHomeConfigSetUp(capReg, ccipHome, newChainSel) +} + +func addDON( + lggr logger.Logger, + ocrSecrets deployment.OCRSecrets, + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + rmnHomeAddress common.Address, + offRamp *offramp.OffRamp, + dest deployment.Chain, + home deployment.Chain, + nodes deployment.Nodes, + ocrParams CCIPOCRParams, +) error { + ocrConfigs, err := internal.BuildOCR3ConfigForCCIPHome( + ocrSecrets, offRamp, dest, nodes, rmnHomeAddress, ocrParams.OCRParameters, ocrParams.CommitOffChainConfig, ocrParams.ExecuteOffChainConfig) + if err != nil { + return err + } + err = createDON(lggr, capReg, ccipHome, ocrConfigs, home, dest.Selector, nodes) + if err != nil { + return err + } + don, err := internal.LatestCCIPDON(capReg) + if err != nil { + return err + } + lggr.Infow("Added DON", "donID", don.Id) + + offrampOCR3Configs, err := internal.BuildSetOCR3ConfigArgs(don.Id, ccipHome, dest.Selector) + if err != nil { + return err + } + lggr.Infow("Setting OCR3 Configs", + "offrampOCR3Configs", offrampOCR3Configs, + "configDigestCommit", hex.EncodeToString(offrampOCR3Configs[cctypes.PluginTypeCCIPCommit].ConfigDigest[:]), + "configDigestExec", hex.EncodeToString(offrampOCR3Configs[cctypes.PluginTypeCCIPExec].ConfigDigest[:]), + "chainSelector", dest.Selector, + ) + + tx, err := offRamp.SetOCR3Configs(dest.DeployerKey, offrampOCR3Configs) + if _, err := deployment.ConfirmIfNoError(dest, tx, err); err != nil { + return err + } + + mapOfframpOCR3Configs := make(map[cctypes.PluginType]offramp.MultiOCR3BaseOCRConfigArgs) + for _, config := range offrampOCR3Configs { + mapOfframpOCR3Configs[cctypes.PluginType(config.OcrPluginType)] = config + } + + for _, pluginType := range []cctypes.PluginType{cctypes.PluginTypeCCIPCommit, cctypes.PluginTypeCCIPExec} { + ocrConfig, err := offRamp.LatestConfigDetails(&bind.CallOpts{ + Context: context.Background(), + }, uint8(pluginType)) + if err != nil { + return err + } + // TODO: assertions to be done as part of full state + // resprentation validation CCIP-3047 + if mapOfframpOCR3Configs[pluginType].ConfigDigest != ocrConfig.ConfigInfo.ConfigDigest { + return fmt.Errorf("%s OCR3 config digest mismatch", pluginType.String()) + } + if mapOfframpOCR3Configs[pluginType].F != ocrConfig.ConfigInfo.F { + return fmt.Errorf("%s OCR3 config F mismatch", pluginType.String()) + } + if mapOfframpOCR3Configs[pluginType].IsSignatureVerificationEnabled != ocrConfig.ConfigInfo.IsSignatureVerificationEnabled { + return fmt.Errorf("%s OCR3 config signature verification mismatch", pluginType.String()) + } + if pluginType == cctypes.PluginTypeCCIPCommit { + // only commit will set signers, exec doesn't need them. + for i, signer := range mapOfframpOCR3Configs[pluginType].Signers { + if !bytes.Equal(signer.Bytes(), ocrConfig.Signers[i].Bytes()) { + return fmt.Errorf("%s OCR3 config signer mismatch", pluginType.String()) + } + } + } + for i, transmitter := range mapOfframpOCR3Configs[pluginType].Transmitters { + if !bytes.Equal(transmitter.Bytes(), ocrConfig.Transmitters[i].Bytes()) { + return fmt.Errorf("%s OCR3 config transmitter mismatch", pluginType.String()) + } + } + } + + return nil +} + +// ValidateCCIPHomeConfigSetUp checks that the commit and exec active and candidate configs are set up correctly +func ValidateCCIPHomeConfigSetUp( + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + chainSel uint64, +) error { + // fetch DONID + donID, err := internal.DonIDForChain(capReg, ccipHome, chainSel) + if err != nil { + return fmt.Errorf("fetch don id for chain: %w", err) + } + // final sanity checks on configs. + commitConfigs, err := ccipHome.GetAllConfigs(&bind.CallOpts{ + //Pending: true, + }, donID, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return fmt.Errorf("get all commit configs: %w", err) + } + commitActiveDigest, err := ccipHome.GetActiveDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return fmt.Errorf("get active commit digest: %w", err) + } + commitCandidateDigest, err := ccipHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return fmt.Errorf("get commit candidate digest: %w", err) + } + if commitConfigs.ActiveConfig.ConfigDigest == [32]byte{} { + return fmt.Errorf( + "active config digest is empty for commit, expected nonempty, donID: %d, cfg: %+v, config digest from GetActiveDigest call: %x, config digest from GetCandidateDigest call: %x", + donID, commitConfigs.ActiveConfig, commitActiveDigest, commitCandidateDigest) + } + if commitConfigs.CandidateConfig.ConfigDigest != [32]byte{} { + return fmt.Errorf( + "candidate config digest is nonempty for commit, expected empty, donID: %d, cfg: %+v, config digest from GetCandidateDigest call: %x, config digest from GetActiveDigest call: %x", + donID, commitConfigs.CandidateConfig, commitCandidateDigest, commitActiveDigest) + } + + execConfigs, err := ccipHome.GetAllConfigs(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) + if err != nil { + return fmt.Errorf("get all exec configs: %w", err) + } + if execConfigs.ActiveConfig.ConfigDigest == [32]byte{} { + return fmt.Errorf("active config digest is empty for exec, expected nonempty, cfg: %v", execConfigs.ActiveConfig) + } + if execConfigs.CandidateConfig.ConfigDigest != [32]byte{} { + return fmt.Errorf("candidate config digest is nonempty for exec, expected empty, cfg: %v", execConfigs.CandidateConfig) + } + return nil +} diff --git a/deployment/ccip/changeset/jobs.go b/deployment/ccip/changeset/cs_jobspec.go similarity index 73% rename from deployment/ccip/changeset/jobs.go rename to deployment/ccip/changeset/cs_jobspec.go index 3a5b0e294d8..2551f193f47 100644 --- a/deployment/ccip/changeset/jobs.go +++ b/deployment/ccip/changeset/cs_jobspec.go @@ -1,25 +1,28 @@ package changeset import ( + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/validate" "github.com/smartcontractkit/chainlink/v2/core/services/relay" ) -// In our case, the only address needed is the cap registry which is actually an env var. -// and will pre-exist for our deployment. So the job specs only depend on the environment operators. -func NewCCIPJobSpecs(nodeIds []string, oc deployment.OffchainClient) (map[string][]string, error) { - nodes, err := deployment.NodeInfo(nodeIds, oc) +var _ deployment.ChangeSet[any] = CCIPCapabilityJobspec + +// CCIPCapabilityJobspec returns the job specs for the CCIP capability. +// The caller needs to propose these job specs to the offchain system. +func CCIPCapabilityJobspec(env deployment.Environment, _ any) (deployment.ChangesetOutput, error) { + nodes, err := deployment.NodeInfo(env.NodeIDs, env.Offchain) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } // Generate a set of brand new job specs for CCIP for a specific environment // (including NOPs) and new addresses. // We want to assign one CCIP capability job to each node. And node with // an addr we'll list as bootstrapper. // Find the bootstrap nodes - nodesToJobSpecs := make(map[string][]string) for _, node := range nodes { var spec string @@ -50,9 +53,13 @@ func NewCCIPJobSpecs(nodeIds []string, oc deployment.OffchainClient) (map[string }) } if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } nodesToJobSpecs[node.NodeID] = append(nodesToJobSpecs[node.NodeID], spec) } - return nodesToJobSpecs, nil + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{}, + AddressBook: nil, + JobSpecs: nodesToJobSpecs, + }, nil } diff --git a/deployment/ccip/changeset/jobspec_test.go b/deployment/ccip/changeset/cs_jobspec_test.go similarity index 100% rename from deployment/ccip/changeset/jobspec_test.go rename to deployment/ccip/changeset/cs_jobspec_test.go diff --git a/deployment/ccip/changeset/cs_prerequisites.go b/deployment/ccip/changeset/cs_prerequisites.go new file mode 100644 index 00000000000..f6c502d9ad5 --- /dev/null +++ b/deployment/ccip/changeset/cs_prerequisites.go @@ -0,0 +1,339 @@ +package changeset + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "golang.org/x/sync/errgroup" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_rmn_contract" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_proxy_contract" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/token_admin_registry" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/weth9" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/multicall3" +) + +var ( + _ deployment.ChangeSet[DeployPrerequisiteConfig] = DeployPrerequisites +) + +// DeployPrerequisites deploys the pre-requisite contracts for CCIP +// pre-requisite contracts are the contracts which can be reused from previous versions of CCIP +// Or the contracts which are already deployed on the chain ( for example, tokens, feeds, etc) +// Caller should update the environment's address book with the returned addresses. +func DeployPrerequisites(env deployment.Environment, cfg DeployPrerequisiteConfig) (deployment.ChangesetOutput, error) { + err := cfg.Validate() + if err != nil { + return deployment.ChangesetOutput{}, errors.Wrapf(deployment.ErrInvalidConfig, "%v", err) + } + ab := deployment.NewMemoryAddressBook() + err = deployPrerequisiteChainContracts(env, ab, cfg.ChainSelectors, cfg.Opts...) + if err != nil { + env.Logger.Errorw("Failed to deploy prerequisite contracts", "err", err, "addressBook", ab) + return deployment.ChangesetOutput{ + AddressBook: ab, + }, fmt.Errorf("failed to deploy prerequisite contracts: %w", err) + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{}, + AddressBook: ab, + JobSpecs: nil, + }, nil +} + +type DeployPrerequisiteContractsOpts struct { + USDCEnabledChains []uint64 + Multicall3Enabled bool +} + +type DeployPrerequisiteConfig struct { + ChainSelectors []uint64 + Opts []PrerequisiteOpt + // TODO handle tokens and feeds in prerequisite config + Tokens map[TokenSymbol]common.Address + Feeds map[TokenSymbol]common.Address +} + +func (c DeployPrerequisiteConfig) Validate() error { + mapAllChainSelectors := make(map[uint64]struct{}) + for _, cs := range c.ChainSelectors { + mapAllChainSelectors[cs] = struct{}{} + if err := deployment.IsValidChainSelector(cs); err != nil { + return fmt.Errorf("invalid chain selector: %d - %w", cs, err) + } + } + return nil +} + +type PrerequisiteOpt func(o *DeployPrerequisiteContractsOpts) + +func WithUSDCChains(chains []uint64) PrerequisiteOpt { + return func(o *DeployPrerequisiteContractsOpts) { + o.USDCEnabledChains = chains + } +} + +func WithMulticall3(enabled bool) PrerequisiteOpt { + return func(o *DeployPrerequisiteContractsOpts) { + o.Multicall3Enabled = enabled + } +} + +func deployPrerequisiteChainContracts(e deployment.Environment, ab deployment.AddressBook, selectors []uint64, opts ...PrerequisiteOpt) error { + state, err := LoadOnchainState(e) + if err != nil { + e.Logger.Errorw("Failed to load existing onchain state", "err") + return err + } + deployGrp := errgroup.Group{} + for _, sel := range selectors { + chain := e.Chains[sel] + deployGrp.Go(func() error { + err := deployPrerequisiteContracts(e, ab, state, chain, opts...) + if err != nil { + e.Logger.Errorw("Failed to deploy prerequisite contracts", "chain", sel, "err", err) + return err + } + return nil + }) + } + return deployGrp.Wait() +} + +// deployPrerequisiteContracts deploys the contracts that can be ported from previous CCIP version to the new one. +// This is only required for staging and test environments where the contracts are not already deployed. +func deployPrerequisiteContracts(e deployment.Environment, ab deployment.AddressBook, state CCIPOnChainState, chain deployment.Chain, opts ...PrerequisiteOpt) error { + deployOpts := &DeployPrerequisiteContractsOpts{} + for _, opt := range opts { + if opt != nil { + opt(deployOpts) + } + } + var isUSDC bool + for _, sel := range deployOpts.USDCEnabledChains { + if sel == chain.Selector { + isUSDC = true + break + } + } + lggr := e.Logger + chainState, chainExists := state.Chains[chain.Selector] + var weth9Contract *weth9.WETH9 + var linkTokenContract *burn_mint_erc677.BurnMintERC677 + var tokenAdminReg *token_admin_registry.TokenAdminRegistry + var registryModule *registry_module_owner_custom.RegistryModuleOwnerCustom + var rmnProxy *rmn_proxy_contract.RMNProxyContract + var r *router.Router + var mc3 *multicall3.Multicall3 + if chainExists { + weth9Contract = chainState.Weth9 + linkTokenContract = chainState.LinkToken + tokenAdminReg = chainState.TokenAdminRegistry + registryModule = chainState.RegistryModule + rmnProxy = chainState.RMNProxyExisting + r = chainState.Router + mc3 = chainState.Multicall3 + } + if rmnProxy == nil { + // we want to replicate the mainnet scenario where RMNProxy is already deployed with some existing RMN + // This will need us to use two different RMNProxy contracts + // 1. RMNProxyNew with RMNRemote - ( deployed later in chain contracts) + // 2. RMNProxyExisting with mockRMN - ( deployed here, replicating the behavior of existing RMNProxy with already set RMN) + rmn, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract] { + rmnAddr, tx2, rmn, err2 := mock_rmn_contract.DeployMockRMNContract( + chain.DeployerKey, + chain.Client, + ) + return deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract]{ + rmnAddr, rmn, tx2, deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy mock RMN", "err", err) + return err + } + lggr.Infow("deployed mock RMN", "addr", rmn.Address) + rmnProxyContract, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract] { + rmnProxyAddr, tx2, rmnProxy, err2 := rmn_proxy_contract.DeployRMNProxyContract( + chain.DeployerKey, + chain.Client, + rmn.Address, + ) + return deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract]{ + rmnProxyAddr, rmnProxy, tx2, deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy RMNProxyNew", "err", err) + return err + } + lggr.Infow("deployed RMNProxyNew", "addr", rmnProxyContract.Address) + rmnProxy = rmnProxyContract.Contract + } + if tokenAdminReg == nil { + tokenAdminRegistry, err := deployment.DeployContract(e.Logger, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*token_admin_registry.TokenAdminRegistry] { + tokenAdminRegistryAddr, tx2, tokenAdminRegistry, err2 := token_admin_registry.DeployTokenAdminRegistry( + chain.DeployerKey, + chain.Client) + return deployment.ContractDeploy[*token_admin_registry.TokenAdminRegistry]{ + tokenAdminRegistryAddr, tokenAdminRegistry, tx2, deployment.NewTypeAndVersion(TokenAdminRegistry, deployment.Version1_5_0), err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy token admin registry", "err", err) + return err + } + e.Logger.Infow("deployed tokenAdminRegistry", "addr", tokenAdminRegistry) + tokenAdminReg = tokenAdminRegistry.Contract + } else { + e.Logger.Infow("tokenAdminRegistry already deployed", "addr", tokenAdminReg.Address) + } + if registryModule == nil { + customRegistryModule, err := deployment.DeployContract(e.Logger, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*registry_module_owner_custom.RegistryModuleOwnerCustom] { + regModAddr, tx2, regMod, err2 := registry_module_owner_custom.DeployRegistryModuleOwnerCustom( + chain.DeployerKey, + chain.Client, + tokenAdminReg.Address()) + return deployment.ContractDeploy[*registry_module_owner_custom.RegistryModuleOwnerCustom]{ + regModAddr, regMod, tx2, deployment.NewTypeAndVersion(RegistryModule, deployment.Version1_5_0), err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy custom registry module", "err", err) + return err + } + e.Logger.Infow("deployed custom registry module", "addr", customRegistryModule) + registryModule = customRegistryModule.Contract + } else { + e.Logger.Infow("custom registry module already deployed", "addr", registryModule.Address) + } + isRegistryAdded, err := tokenAdminReg.IsRegistryModule(nil, registryModule.Address()) + if err != nil { + e.Logger.Errorw("Failed to check if registry module is added on token admin registry", "err", err) + return fmt.Errorf("failed to check if registry module is added on token admin registry: %w", err) + } + if !isRegistryAdded { + tx, err := tokenAdminReg.AddRegistryModule(chain.DeployerKey, registryModule.Address()) + if err != nil { + e.Logger.Errorw("Failed to assign registry module on token admin registry", "err", err) + return fmt.Errorf("failed to assign registry module on token admin registry: %w", err) + } + + _, err = chain.Confirm(tx) + if err != nil { + e.Logger.Errorw("Failed to confirm assign registry module on token admin registry", "err", err) + return fmt.Errorf("failed to confirm assign registry module on token admin registry: %w", err) + } + e.Logger.Infow("assigned registry module on token admin registry") + } + if weth9Contract == nil { + weth, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*weth9.WETH9] { + weth9Addr, tx2, weth9c, err2 := weth9.DeployWETH9( + chain.DeployerKey, + chain.Client, + ) + return deployment.ContractDeploy[*weth9.WETH9]{ + weth9Addr, weth9c, tx2, deployment.NewTypeAndVersion(WETH9, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy weth9", "err", err) + return err + } + lggr.Infow("deployed weth9", "addr", weth.Address) + weth9Contract = weth.Contract + } else { + lggr.Infow("weth9 already deployed", "addr", weth9Contract.Address) + } + if linkTokenContract == nil { + linkToken, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677] { + linkTokenAddr, tx2, linkToken, err2 := burn_mint_erc677.DeployBurnMintERC677( + chain.DeployerKey, + chain.Client, + "Link Token", + "LINK", + uint8(18), + big.NewInt(0).Mul(big.NewInt(1e9), big.NewInt(1e18)), + ) + return deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677]{ + linkTokenAddr, linkToken, tx2, deployment.NewTypeAndVersion(LinkToken, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy linkToken", "err", err) + return err + } + lggr.Infow("deployed linkToken", "addr", linkToken.Address) + } else { + lggr.Infow("linkToken already deployed", "addr", linkTokenContract.Address) + } + // if router is not already deployed, we deploy it + if r == nil { + routerContract, err := deployment.DeployContract(e.Logger, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*router.Router] { + routerAddr, tx2, routerC, err2 := router.DeployRouter( + chain.DeployerKey, + chain.Client, + weth9Contract.Address(), + rmnProxy.Address(), + ) + return deployment.ContractDeploy[*router.Router]{ + routerAddr, routerC, tx2, deployment.NewTypeAndVersion(Router, deployment.Version1_2_0), err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy router", "err", err) + return err + } + e.Logger.Infow("deployed router", "addr", routerContract.Address) + r = routerContract.Contract + } else { + e.Logger.Infow("router already deployed", "addr", chainState.Router.Address) + } + if deployOpts.Multicall3Enabled && mc3 == nil { + multicall3Contract, err := deployment.DeployContract(e.Logger, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*multicall3.Multicall3] { + multicall3Addr, tx2, multicall3Wrapper, err2 := multicall3.DeployMulticall3( + chain.DeployerKey, + chain.Client, + ) + return deployment.ContractDeploy[*multicall3.Multicall3]{ + multicall3Addr, multicall3Wrapper, tx2, deployment.NewTypeAndVersion(Multicall3, deployment.Version1_0_0), err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy ccip multicall", "err", err) + return err + } + e.Logger.Infow("deployed ccip multicall", "addr", multicall3Contract.Address) + } else { + e.Logger.Info("ccip multicall already deployed", "addr", mc3.Address) + } + if isUSDC { + token, pool, messenger, transmitter, err1 := DeployUSDC(e.Logger, chain, ab, rmnProxy.Address(), r.Address()) + if err1 != nil { + return err1 + } + e.Logger.Infow("Deployed USDC contracts", + "chainSelector", chain.Selector, + "token", token.Address(), + "pool", pool.Address(), + "transmitter", transmitter.Address(), + "messenger", messenger.Address(), + ) + } + return nil +} diff --git a/deployment/ccip/changeset/prerequisites_test.go b/deployment/ccip/changeset/cs_prerequisites_test.go similarity index 100% rename from deployment/ccip/changeset/prerequisites_test.go rename to deployment/ccip/changeset/cs_prerequisites_test.go diff --git a/deployment/ccip/changeset/deploy_chain.go b/deployment/ccip/changeset/deploy_chain.go deleted file mode 100644 index d0f44724070..00000000000 --- a/deployment/ccip/changeset/deploy_chain.go +++ /dev/null @@ -1,50 +0,0 @@ -package changeset - -import ( - "fmt" - - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" -) - -var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts - -// DeployChainContracts deploys all new CCIP v1.6 or later contracts for the given chains. -// It returns the new addresses for the contracts. -// DeployChainContracts is idempotent. If there is an error, it will return the successfully deployed addresses and the error so that the caller can call the -// changeset again with the same input to retry the failed deployment. -// Caller should update the environment's address book with the returned addresses. -func DeployChainContracts(env deployment.Environment, c DeployChainContractsConfig) (deployment.ChangesetOutput, error) { - if err := c.Validate(); err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("invalid DeployChainContractsConfig: %w", err) - } - newAddresses := deployment.NewMemoryAddressBook() - err := deployChainContractsForChains(env, newAddresses, c.HomeChainSelector, c.ChainSelectors) - if err != nil { - env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "newAddresses", newAddresses) - return deployment.ChangesetOutput{AddressBook: newAddresses}, deployment.MaybeDataErr(err) - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: newAddresses, - JobSpecs: nil, - }, nil -} - -type DeployChainContractsConfig struct { - ChainSelectors []uint64 - HomeChainSelector uint64 -} - -func (c DeployChainContractsConfig) Validate() error { - for _, cs := range c.ChainSelectors { - if err := deployment.IsValidChainSelector(cs); err != nil { - return fmt.Errorf("invalid chain selector: %d - %w", cs, err) - } - } - if err := deployment.IsValidChainSelector(c.HomeChainSelector); err != nil { - return fmt.Errorf("invalid home chain selector: %d - %w", c.HomeChainSelector, err) - } - return nil -} diff --git a/deployment/ccip/changeset/deploy_home_chain.go b/deployment/ccip/changeset/deploy_home_chain.go deleted file mode 100644 index 881f7c386c3..00000000000 --- a/deployment/ccip/changeset/deploy_home_chain.go +++ /dev/null @@ -1,561 +0,0 @@ -package changeset - -import ( - "bytes" - "context" - "encoding/hex" - "encoding/json" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - "golang.org/x/exp/maps" - - "github.com/smartcontractkit/chainlink-ccip/chainconfig" - "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" - cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" - p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" -) - -// DeployCapReg deploys the CapabilitiesRegistry contract if it is not already deployed -// and returns a deployment.ContractDeploy struct with the address and contract instance. -func DeployCapReg( - lggr logger.Logger, - state CCIPOnChainState, - ab deployment.AddressBook, - chain deployment.Chain, -) (*deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) { - homeChainState, exists := state.Chains[chain.Selector] - if exists { - cr := homeChainState.CapabilityRegistry - if cr != nil { - lggr.Infow("Found CapabilitiesRegistry in chain state", "address", cr.Address().String()) - return &deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry]{ - Address: cr.Address(), Contract: cr, Tv: deployment.NewTypeAndVersion(CapabilitiesRegistry, deployment.Version1_0_0), - }, nil - } - } - capReg, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry] { - crAddr, tx, cr, err2 := capabilities_registry.DeployCapabilitiesRegistry( - chain.DeployerKey, - chain.Client, - ) - return deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry]{ - Address: crAddr, Contract: cr, Tv: deployment.NewTypeAndVersion(CapabilitiesRegistry, deployment.Version1_0_0), Tx: tx, Err: err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy capreg", "err", err) - return nil, err - } - return capReg, nil -} - -func deployHomeChain( - lggr logger.Logger, - e deployment.Environment, - ab deployment.AddressBook, - chain deployment.Chain, - rmnHomeStatic rmn_home.RMNHomeStaticConfig, - rmnHomeDynamic rmn_home.RMNHomeDynamicConfig, - nodeOps []capabilities_registry.CapabilitiesRegistryNodeOperator, - nodeP2PIDsPerNodeOpAdmin map[string][][32]byte, -) (*deployment.ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) { - // load existing state - state, err := LoadOnchainState(e) - if err != nil { - return nil, fmt.Errorf("failed to load onchain state: %w", err) - } - // Deploy CapabilitiesRegistry, CCIPHome, RMNHome - capReg, err := DeployCapReg(lggr, state, ab, chain) - if err != nil { - return nil, err - } - - lggr.Infow("deployed/connected to capreg", "addr", capReg.Address) - ccipHome, err := deployment.DeployContract( - lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*ccip_home.CCIPHome] { - ccAddr, tx, cc, err2 := ccip_home.DeployCCIPHome( - chain.DeployerKey, - chain.Client, - capReg.Address, - ) - return deployment.ContractDeploy[*ccip_home.CCIPHome]{ - Address: ccAddr, Tv: deployment.NewTypeAndVersion(CCIPHome, deployment.Version1_6_0_dev), Tx: tx, Err: err2, Contract: cc, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy CCIPHome", "err", err) - return nil, err - } - lggr.Infow("deployed CCIPHome", "addr", ccipHome.Address) - - rmnHome, err := deployment.DeployContract( - lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*rmn_home.RMNHome] { - rmnAddr, tx, rmn, err2 := rmn_home.DeployRMNHome( - chain.DeployerKey, - chain.Client, - ) - return deployment.ContractDeploy[*rmn_home.RMNHome]{ - Address: rmnAddr, Tv: deployment.NewTypeAndVersion(RMNHome, deployment.Version1_6_0_dev), Tx: tx, Err: err2, Contract: rmn, - } - }, - ) - if err != nil { - lggr.Errorw("Failed to deploy RMNHome", "err", err) - return nil, err - } - lggr.Infow("deployed RMNHome", "addr", rmnHome.Address) - - // considering the RMNHome is recently deployed, there is no digest to overwrite - tx, err := rmnHome.Contract.SetCandidate(chain.DeployerKey, rmnHomeStatic, rmnHomeDynamic, [32]byte{}) - if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { - lggr.Errorw("Failed to set candidate on RMNHome", "err", err) - return nil, err - } - - rmnCandidateDigest, err := rmnHome.Contract.GetCandidateDigest(nil) - if err != nil { - lggr.Errorw("Failed to get RMNHome candidate digest", "err", err) - return nil, err - } - - tx, err = rmnHome.Contract.PromoteCandidateAndRevokeActive(chain.DeployerKey, rmnCandidateDigest, [32]byte{}) - if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { - lggr.Errorw("Failed to promote candidate and revoke active on RMNHome", "err", err) - return nil, err - } - - rmnActiveDigest, err := rmnHome.Contract.GetActiveDigest(nil) - if err != nil { - lggr.Errorw("Failed to get RMNHome active digest", "err", err) - return nil, err - } - lggr.Infow("Got rmn home active digest", "digest", rmnActiveDigest) - - if rmnActiveDigest != rmnCandidateDigest { - lggr.Errorw("RMNHome active digest does not match previously candidate digest", - "active", rmnActiveDigest, "candidate", rmnCandidateDigest) - return nil, errors.New("RMNHome active digest does not match candidate digest") - } - - tx, err = capReg.Contract.AddCapabilities(chain.DeployerKey, []capabilities_registry.CapabilitiesRegistryCapability{ - { - LabelledName: internal.CapabilityLabelledName, - Version: internal.CapabilityVersion, - CapabilityType: 2, // consensus. not used (?) - ResponseType: 0, // report. not used (?) - ConfigurationContract: ccipHome.Address, - }, - }) - if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { - lggr.Errorw("Failed to add capabilities", "err", err) - return nil, err - } - - tx, err = capReg.Contract.AddNodeOperators(chain.DeployerKey, nodeOps) - txBlockNum, err := deployment.ConfirmIfNoError(chain, tx, err) - if err != nil { - lggr.Errorw("Failed to add node operators", "err", err) - return nil, err - } - addedEvent, err := capReg.Contract.FilterNodeOperatorAdded(&bind.FilterOpts{ - Start: txBlockNum, - Context: context.Background(), - }, nil, nil) - if err != nil { - lggr.Errorw("Failed to filter NodeOperatorAdded event", "err", err) - return capReg, err - } - // Need to fetch nodeoperators ids to be able to add nodes for corresponding node operators - p2pIDsByNodeOpId := make(map[uint32][][32]byte) - for addedEvent.Next() { - for nopName, p2pId := range nodeP2PIDsPerNodeOpAdmin { - if addedEvent.Event.Name == nopName { - lggr.Infow("Added node operator", "admin", addedEvent.Event.Admin, "name", addedEvent.Event.Name) - p2pIDsByNodeOpId[addedEvent.Event.NodeOperatorId] = p2pId - } - } - } - if len(p2pIDsByNodeOpId) != len(nodeP2PIDsPerNodeOpAdmin) { - lggr.Errorw("Failed to add all node operators", "added", maps.Keys(p2pIDsByNodeOpId), "expected", maps.Keys(nodeP2PIDsPerNodeOpAdmin)) - return capReg, errors.New("failed to add all node operators") - } - // Adds initial set of nodes to CR, who all have the CCIP capability - if err := AddNodes(lggr, capReg.Contract, chain, p2pIDsByNodeOpId); err != nil { - return capReg, err - } - return capReg, nil -} - -func isEqualCapabilitiesRegistryNodeParams(a, b capabilities_registry.CapabilitiesRegistryNodeParams) (bool, error) { - aBytes, err := json.Marshal(a) - if err != nil { - return false, err - } - bBytes, err := json.Marshal(b) - if err != nil { - return false, err - } - return bytes.Equal(aBytes, bBytes), nil -} - -func AddNodes( - lggr logger.Logger, - capReg *capabilities_registry.CapabilitiesRegistry, - chain deployment.Chain, - p2pIDsByNodeOpId map[uint32][][32]byte, -) error { - var nodeParams []capabilities_registry.CapabilitiesRegistryNodeParams - nodes, err := capReg.GetNodes(nil) - if err != nil { - return err - } - existingNodeParams := make(map[p2ptypes.PeerID]capabilities_registry.CapabilitiesRegistryNodeParams) - for _, node := range nodes { - existingNodeParams[node.P2pId] = capabilities_registry.CapabilitiesRegistryNodeParams{ - NodeOperatorId: node.NodeOperatorId, - Signer: node.Signer, - P2pId: node.P2pId, - HashedCapabilityIds: node.HashedCapabilityIds, - } - } - for nopID, p2pIDs := range p2pIDsByNodeOpId { - for _, p2pID := range p2pIDs { - // if any p2pIDs are empty throw error - if bytes.Equal(p2pID[:], make([]byte, 32)) { - return errors.Wrapf(errors.New("empty p2pID"), "p2pID: %x selector: %d", p2pID, chain.Selector) - } - nodeParam := capabilities_registry.CapabilitiesRegistryNodeParams{ - NodeOperatorId: nopID, - Signer: p2pID, // Not used in tests - P2pId: p2pID, - EncryptionPublicKey: p2pID, // Not used in tests - HashedCapabilityIds: [][32]byte{internal.CCIPCapabilityID}, - } - if existing, ok := existingNodeParams[p2pID]; ok { - if isEqual, err := isEqualCapabilitiesRegistryNodeParams(existing, nodeParam); err != nil && isEqual { - lggr.Infow("Node already exists", "p2pID", p2pID) - continue - } - } - - nodeParams = append(nodeParams, nodeParam) - } - } - if len(nodeParams) == 0 { - lggr.Infow("No new nodes to add") - return nil - } - tx, err := capReg.AddNodes(chain.DeployerKey, nodeParams) - if err != nil { - lggr.Errorw("Failed to add nodes", "err", deployment.MaybeDataErr(err)) - return err - } - _, err = chain.Confirm(tx) - return err -} - -func SetupConfigInfo(chainSelector uint64, readers [][32]byte, fChain uint8, cfg []byte) ccip_home.CCIPHomeChainConfigArgs { - return ccip_home.CCIPHomeChainConfigArgs{ - ChainSelector: chainSelector, - ChainConfig: ccip_home.CCIPHomeChainConfig{ - Readers: readers, - FChain: fChain, - Config: cfg, - }, - } -} - -func AddChainConfig( - lggr logger.Logger, - h deployment.Chain, - ccipConfig *ccip_home.CCIPHome, - chainSelector uint64, - p2pIDs [][32]byte, -) (ccip_home.CCIPHomeChainConfigArgs, error) { - // First Add ChainConfig that includes all p2pIDs as readers - encodedExtraChainConfig, err := chainconfig.EncodeChainConfig(chainconfig.ChainConfig{ - GasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(1000), - DAGasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(0), - OptimisticConfirmations: 1, - }) - if err != nil { - return ccip_home.CCIPHomeChainConfigArgs{}, err - } - chainConfig := SetupConfigInfo(chainSelector, p2pIDs, uint8(len(p2pIDs)/3), encodedExtraChainConfig) - tx, err := ccipConfig.ApplyChainConfigUpdates(h.DeployerKey, nil, []ccip_home.CCIPHomeChainConfigArgs{ - chainConfig, - }) - if _, err := deployment.ConfirmIfNoError(h, tx, err); err != nil { - return ccip_home.CCIPHomeChainConfigArgs{}, err - } - lggr.Infow("Applied chain config updates", "chainConfig", chainConfig) - return chainConfig, nil -} - -// CreateDON creates one DON with 2 plugins (commit and exec) -// It first set a new candidate for the DON with the first plugin type and AddDON on capReg -// Then for subsequent operations it uses UpdateDON to promote the first plugin to the active deployment -// and to set candidate and promote it for the second plugin -func CreateDON( - lggr logger.Logger, - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - ocr3Configs map[cctypes.PluginType]ccip_home.CCIPHomeOCR3Config, - home deployment.Chain, - newChainSel uint64, - nodes deployment.Nodes, -) error { - commitConfig, ok := ocr3Configs[cctypes.PluginTypeCCIPCommit] - if !ok { - return fmt.Errorf("missing commit plugin in ocr3Configs") - } - - execConfig, ok := ocr3Configs[cctypes.PluginTypeCCIPExec] - if !ok { - return fmt.Errorf("missing exec plugin in ocr3Configs") - } - - latestDon, err := internal.LatestCCIPDON(capReg) - if err != nil { - return err - } - - donID := latestDon.Id + 1 - - err = internal.SetupCommitDON(donID, commitConfig, capReg, home, nodes, ccipHome) - if err != nil { - return fmt.Errorf("setup commit don: %w", err) - } - - // TODO: bug in contract causing this to not work as expected. - err = internal.SetupExecDON(donID, execConfig, capReg, home, nodes, ccipHome) - if err != nil { - return fmt.Errorf("setup exec don: %w", err) - } - return ValidateCCIPHomeConfigSetUp(capReg, ccipHome, newChainSel) -} - -// SetCandidateCommitPluginWithAddDonOps sets the candidate commit config by calling setCandidate on CCIPHome contract through the AddDON call on CapReg contract -// This should be done first before calling any other UpdateDON calls -// This proposes to set up OCR3 config for the commit plugin for the DON -func NewDonWithCandidateOp( - donID uint32, - pluginConfig ccip_home.CCIPHomeOCR3Config, - capReg *capabilities_registry.CapabilitiesRegistry, - nodes deployment.Nodes, -) (mcms.Operation, error) { - encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack( - "setCandidate", - donID, - pluginConfig.PluginType, - pluginConfig, - [32]byte{}, - ) - if err != nil { - return mcms.Operation{}, fmt.Errorf("pack set candidate call: %w", err) - } - addDonTx, err := capReg.AddDON(deployment.SimTransactOpts(), nodes.PeerIDs(), []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - { - CapabilityId: internal.CCIPCapabilityID, - Config: encodedSetCandidateCall, - }, - }, false, false, nodes.DefaultF()) - if err != nil { - return mcms.Operation{}, fmt.Errorf("could not generate add don tx w/ commit config: %w", err) - } - return mcms.Operation{ - To: capReg.Address(), - Data: addDonTx.Data(), - Value: big.NewInt(0), - }, nil -} - -// ValidateCCIPHomeConfigSetUp checks that the commit and exec active and candidate configs are set up correctly -func ValidateCCIPHomeConfigSetUp( - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - chainSel uint64, -) error { - // fetch DONID - donID, err := internal.DonIDForChain(capReg, ccipHome, chainSel) - if err != nil { - return fmt.Errorf("fetch don id for chain: %w", err) - } - // final sanity checks on configs. - commitConfigs, err := ccipHome.GetAllConfigs(&bind.CallOpts{ - //Pending: true, - }, donID, uint8(cctypes.PluginTypeCCIPCommit)) - if err != nil { - return fmt.Errorf("get all commit configs: %w", err) - } - commitActiveDigest, err := ccipHome.GetActiveDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) - if err != nil { - return fmt.Errorf("get active commit digest: %w", err) - } - commitCandidateDigest, err := ccipHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) - if err != nil { - return fmt.Errorf("get commit candidate digest: %w", err) - } - if commitConfigs.ActiveConfig.ConfigDigest == [32]byte{} { - return fmt.Errorf( - "active config digest is empty for commit, expected nonempty, donID: %d, cfg: %+v, config digest from GetActiveDigest call: %x, config digest from GetCandidateDigest call: %x", - donID, commitConfigs.ActiveConfig, commitActiveDigest, commitCandidateDigest) - } - if commitConfigs.CandidateConfig.ConfigDigest != [32]byte{} { - return fmt.Errorf( - "candidate config digest is nonempty for commit, expected empty, donID: %d, cfg: %+v, config digest from GetCandidateDigest call: %x, config digest from GetActiveDigest call: %x", - donID, commitConfigs.CandidateConfig, commitCandidateDigest, commitActiveDigest) - } - - execConfigs, err := ccipHome.GetAllConfigs(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) - if err != nil { - return fmt.Errorf("get all exec configs: %w", err) - } - if execConfigs.ActiveConfig.ConfigDigest == [32]byte{} { - return fmt.Errorf("active config digest is empty for exec, expected nonempty, cfg: %v", execConfigs.ActiveConfig) - } - if execConfigs.CandidateConfig.ConfigDigest != [32]byte{} { - return fmt.Errorf("candidate config digest is nonempty for exec, expected empty, cfg: %v", execConfigs.CandidateConfig) - } - return nil -} - -func addDON( - lggr logger.Logger, - ocrSecrets deployment.OCRSecrets, - capReg *capabilities_registry.CapabilitiesRegistry, - ccipHome *ccip_home.CCIPHome, - rmnHomeAddress common.Address, - offRamp *offramp.OffRamp, - dest deployment.Chain, - home deployment.Chain, - nodes deployment.Nodes, - ocrParams CCIPOCRParams, -) error { - ocrConfigs, err := internal.BuildOCR3ConfigForCCIPHome( - ocrSecrets, offRamp, dest, nodes, rmnHomeAddress, ocrParams.OCRParameters, ocrParams.CommitOffChainConfig, ocrParams.ExecuteOffChainConfig) - if err != nil { - return err - } - err = CreateDON(lggr, capReg, ccipHome, ocrConfigs, home, dest.Selector, nodes) - if err != nil { - return err - } - don, err := internal.LatestCCIPDON(capReg) - if err != nil { - return err - } - lggr.Infow("Added DON", "donID", don.Id) - - offrampOCR3Configs, err := internal.BuildSetOCR3ConfigArgs(don.Id, ccipHome, dest.Selector) - if err != nil { - return err - } - lggr.Infow("Setting OCR3 Configs", - "offrampOCR3Configs", offrampOCR3Configs, - "configDigestCommit", hex.EncodeToString(offrampOCR3Configs[cctypes.PluginTypeCCIPCommit].ConfigDigest[:]), - "configDigestExec", hex.EncodeToString(offrampOCR3Configs[cctypes.PluginTypeCCIPExec].ConfigDigest[:]), - "chainSelector", dest.Selector, - ) - - tx, err := offRamp.SetOCR3Configs(dest.DeployerKey, offrampOCR3Configs) - if _, err := deployment.ConfirmIfNoError(dest, tx, err); err != nil { - return err - } - - mapOfframpOCR3Configs := make(map[cctypes.PluginType]offramp.MultiOCR3BaseOCRConfigArgs) - for _, config := range offrampOCR3Configs { - mapOfframpOCR3Configs[cctypes.PluginType(config.OcrPluginType)] = config - } - - for _, pluginType := range []cctypes.PluginType{cctypes.PluginTypeCCIPCommit, cctypes.PluginTypeCCIPExec} { - ocrConfig, err := offRamp.LatestConfigDetails(&bind.CallOpts{ - Context: context.Background(), - }, uint8(pluginType)) - if err != nil { - return err - } - // TODO: assertions to be done as part of full state - // resprentation validation CCIP-3047 - if mapOfframpOCR3Configs[pluginType].ConfigDigest != ocrConfig.ConfigInfo.ConfigDigest { - return fmt.Errorf("%s OCR3 config digest mismatch", pluginType.String()) - } - if mapOfframpOCR3Configs[pluginType].F != ocrConfig.ConfigInfo.F { - return fmt.Errorf("%s OCR3 config F mismatch", pluginType.String()) - } - if mapOfframpOCR3Configs[pluginType].IsSignatureVerificationEnabled != ocrConfig.ConfigInfo.IsSignatureVerificationEnabled { - return fmt.Errorf("%s OCR3 config signature verification mismatch", pluginType.String()) - } - if pluginType == cctypes.PluginTypeCCIPCommit { - // only commit will set signers, exec doesn't need them. - for i, signer := range mapOfframpOCR3Configs[pluginType].Signers { - if !bytes.Equal(signer.Bytes(), ocrConfig.Signers[i].Bytes()) { - return fmt.Errorf("%s OCR3 config signer mismatch", pluginType.String()) - } - } - } - for i, transmitter := range mapOfframpOCR3Configs[pluginType].Transmitters { - if !bytes.Equal(transmitter.Bytes(), ocrConfig.Transmitters[i].Bytes()) { - return fmt.Errorf("%s OCR3 config transmitter mismatch", pluginType.String()) - } - } - } - - return nil -} - -func ApplyChainConfigUpdatesOp( - e deployment.Environment, - state CCIPOnChainState, - homeChainSel uint64, - chains []uint64, -) (mcms.Operation, error) { - nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) - if err != nil { - return mcms.Operation{}, err - } - encodedExtraChainConfig, err := chainconfig.EncodeChainConfig(chainconfig.ChainConfig{ - GasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(1000), - DAGasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(0), - OptimisticConfirmations: 1, - }) - if err != nil { - return mcms.Operation{}, err - } - var chainConfigUpdates []ccip_home.CCIPHomeChainConfigArgs - for _, chainSel := range chains { - chainConfig := SetupConfigInfo(chainSel, nodes.NonBootstraps().PeerIDs(), - nodes.DefaultF(), encodedExtraChainConfig) - chainConfigUpdates = append(chainConfigUpdates, chainConfig) - } - - addChain, err := state.Chains[homeChainSel].CCIPHome.ApplyChainConfigUpdates( - deployment.SimTransactOpts(), - nil, - chainConfigUpdates, - ) - if err != nil { - return mcms.Operation{}, err - } - return mcms.Operation{ - To: state.Chains[homeChainSel].CCIPHome.Address(), - Data: addChain.Data(), - Value: big.NewInt(0), - }, nil -} diff --git a/deployment/ccip/changeset/deploy_test.go b/deployment/ccip/changeset/deploy_test.go deleted file mode 100644 index fb5729c5b77..00000000000 --- a/deployment/ccip/changeset/deploy_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package changeset - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -func TestDeployCCIPContracts(t *testing.T) { - lggr := logger.TestLogger(t) - e := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 2, 4, nil) - // Deploy all the CCIP contracts. - state, err := LoadOnchainState(e.Env) - require.NoError(t, err) - snap, err := state.View(e.Env.AllChainSelectors()) - require.NoError(t, err) - - // Assert expect every deployed address to be in the address book. - // TODO (CCIP-3047): Add the rest of CCIPv2 representation - b, err := json.MarshalIndent(snap, "", " ") - require.NoError(t, err) - fmt.Println(string(b)) -} diff --git a/deployment/ccip/changeset/home_chain.go b/deployment/ccip/changeset/home_chain.go deleted file mode 100644 index e88db2bcfe0..00000000000 --- a/deployment/ccip/changeset/home_chain.go +++ /dev/null @@ -1,74 +0,0 @@ -package changeset - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" -) - -var _ deployment.ChangeSet[DeployHomeChainConfig] = DeployHomeChain - -// DeployHomeChain is a separate changeset because it is a standalone deployment performed once in home chain for the entire CCIP deployment. -func DeployHomeChain(env deployment.Environment, cfg DeployHomeChainConfig) (deployment.ChangesetOutput, error) { - err := cfg.Validate() - if err != nil { - return deployment.ChangesetOutput{}, errors.Wrapf(deployment.ErrInvalidConfig, "%v", err) - } - ab := deployment.NewMemoryAddressBook() - // Note we also deploy the cap reg. - _, err = deployHomeChain(env.Logger, env, ab, env.Chains[cfg.HomeChainSel], cfg.RMNStaticConfig, cfg.RMNDynamicConfig, cfg.NodeOperators, cfg.NodeP2PIDsPerNodeOpAdmin) - if err != nil { - env.Logger.Errorw("Failed to deploy cap reg", "err", err, "addresses", env.ExistingAddresses) - return deployment.ChangesetOutput{ - AddressBook: ab, - }, err - } - - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: ab, - JobSpecs: nil, - }, nil -} - -type DeployHomeChainConfig struct { - HomeChainSel uint64 - RMNStaticConfig rmn_home.RMNHomeStaticConfig - RMNDynamicConfig rmn_home.RMNHomeDynamicConfig - NodeOperators []capabilities_registry.CapabilitiesRegistryNodeOperator - NodeP2PIDsPerNodeOpAdmin map[string][][32]byte -} - -func (c DeployHomeChainConfig) Validate() error { - if c.HomeChainSel == 0 { - return fmt.Errorf("home chain selector must be set") - } - if c.RMNDynamicConfig.OffchainConfig == nil { - return fmt.Errorf("offchain config for RMNHomeDynamicConfig must be set") - } - if c.RMNStaticConfig.OffchainConfig == nil { - return fmt.Errorf("offchain config for RMNHomeStaticConfig must be set") - } - if len(c.NodeOperators) == 0 { - return fmt.Errorf("node operators must be set") - } - for _, nop := range c.NodeOperators { - if nop.Admin == (common.Address{}) { - return fmt.Errorf("node operator admin address must be set") - } - if nop.Name == "" { - return fmt.Errorf("node operator name must be set") - } - if len(c.NodeP2PIDsPerNodeOpAdmin[nop.Name]) == 0 { - return fmt.Errorf("node operator %s must have node p2p ids provided", nop.Name) - } - } - - return nil -} diff --git a/deployment/ccip/changeset/initial_add_chain.go b/deployment/ccip/changeset/initial_add_chain.go deleted file mode 100644 index 841f2014204..00000000000 --- a/deployment/ccip/changeset/initial_add_chain.go +++ /dev/null @@ -1,208 +0,0 @@ -package changeset - -import ( - "fmt" - "os" - "slices" - "sort" - "time" - - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" - "github.com/smartcontractkit/chainlink/deployment/common/types" -) - -var _ deployment.ChangeSet[NewChainsConfig] = ConfigureNewChains - -// ConfigureNewChains enables new chains as destination for CCIP -// It performs the following steps: -// - AddChainConfig + AddDON (candidate->primary promotion i.e. init) on the home chain -// - SetOCR3Config on the remote chain -// ConfigureNewChains assumes that the home chain is already enabled and all CCIP contracts are already deployed. -func ConfigureNewChains(env deployment.Environment, c NewChainsConfig) (deployment.ChangesetOutput, error) { - if err := c.Validate(); err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("invalid NewChainsConfig: %w", err) - } - err := configureChain(env, c) - if err != nil { - env.Logger.Errorw("Failed to configure chain", "err", err) - return deployment.ChangesetOutput{}, deployment.MaybeDataErr(err) - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: nil, - JobSpecs: nil, - }, nil -} - -type USDCConfig struct { - EnabledChains []uint64 - USDCAttestationConfig - CCTPTokenConfig map[ccipocr3.ChainSelector]pluginconfig.USDCCCTPTokenConfig -} - -func (cfg USDCConfig) EnabledChainMap() map[uint64]bool { - m := make(map[uint64]bool) - for _, chain := range cfg.EnabledChains { - m[chain] = true - } - return m -} - -func (cfg USDCConfig) ToTokenDataObserverConfig() []pluginconfig.TokenDataObserverConfig { - return []pluginconfig.TokenDataObserverConfig{{ - Type: pluginconfig.USDCCCTPHandlerType, - Version: "1.0", - USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ - Tokens: cfg.CCTPTokenConfig, - AttestationAPI: cfg.API, - AttestationAPITimeout: cfg.APITimeout, - AttestationAPIInterval: cfg.APIInterval, - }, - }} -} - -type USDCAttestationConfig struct { - API string - APITimeout *config.Duration - APIInterval *config.Duration -} - -type CCIPOCRParams struct { - OCRParameters types.OCRParameters - CommitOffChainConfig pluginconfig.CommitOffchainConfig - ExecuteOffChainConfig pluginconfig.ExecuteOffchainConfig -} - -func (p CCIPOCRParams) Validate() error { - if err := p.OCRParameters.Validate(); err != nil { - return fmt.Errorf("invalid OCR parameters: %w", err) - } - if err := p.CommitOffChainConfig.Validate(); err != nil { - return fmt.Errorf("invalid commit off-chain config: %w", err) - } - if err := p.ExecuteOffChainConfig.Validate(); err != nil { - return fmt.Errorf("invalid execute off-chain config: %w", err) - } - return nil -} - -type NewChainsConfig struct { - HomeChainSel uint64 - FeedChainSel uint64 - ChainsToDeploy []uint64 - TokenConfig TokenConfig - USDCConfig USDCConfig - // For setting OCR configuration - OCRSecrets deployment.OCRSecrets - OCRParams map[uint64]CCIPOCRParams -} - -func (c NewChainsConfig) Validate() error { - if err := deployment.IsValidChainSelector(c.HomeChainSel); err != nil { - return fmt.Errorf("invalid home chain selector: %d - %w", c.HomeChainSel, err) - } - if err := deployment.IsValidChainSelector(c.FeedChainSel); err != nil { - return fmt.Errorf("invalid feed chain selector: %d - %w", c.FeedChainSel, err) - } - mapChainsToDeploy := make(map[uint64]bool) - for _, cs := range c.ChainsToDeploy { - mapChainsToDeploy[cs] = true - if err := deployment.IsValidChainSelector(cs); err != nil { - return fmt.Errorf("invalid chain selector: %d - %w", cs, err) - } - } - for token := range c.TokenConfig.TokenSymbolToInfo { - if err := c.TokenConfig.TokenSymbolToInfo[token].Validate(); err != nil { - return fmt.Errorf("invalid token config for token %s: %w", token, err) - } - } - if c.OCRSecrets.IsEmpty() { - return fmt.Errorf("no OCR secrets provided") - } - usdcEnabledChainMap := c.USDCConfig.EnabledChainMap() - for chain := range usdcEnabledChainMap { - if _, exists := mapChainsToDeploy[chain]; !exists { - return fmt.Errorf("chain %d is not in chains to deploy", chain) - } - if err := deployment.IsValidChainSelector(chain); err != nil { - return fmt.Errorf("invalid chain selector: %d - %w", chain, err) - } - } - for chain := range c.USDCConfig.CCTPTokenConfig { - if _, exists := mapChainsToDeploy[uint64(chain)]; !exists { - return fmt.Errorf("chain %d is not in chains to deploy", chain) - } - if _, exists := usdcEnabledChainMap[uint64(chain)]; !exists { - return fmt.Errorf("chain %d is not enabled in USDC config", chain) - } - if err := deployment.IsValidChainSelector(uint64(chain)); err != nil { - return fmt.Errorf("invalid chain selector: %d - %w", chain, err) - } - } - // Validate OCR params - var ocrChains []uint64 - for chain, ocrParams := range c.OCRParams { - ocrChains = append(ocrChains, chain) - if _, exists := mapChainsToDeploy[chain]; !exists { - return fmt.Errorf("chain %d is not in chains to deploy", chain) - } - if err := ocrParams.Validate(); err != nil { - return fmt.Errorf("invalid OCR params for chain %d: %w", chain, err) - } - } - sort.Slice(ocrChains, func(i, j int) bool { return ocrChains[i] < ocrChains[j] }) - sort.Slice(c.ChainsToDeploy, func(i, j int) bool { return c.ChainsToDeploy[i] < c.ChainsToDeploy[j] }) - if !slices.Equal(ocrChains, c.ChainsToDeploy) { - return fmt.Errorf("mismatch in given OCR params and chains to deploy") - } - return nil -} - -func DefaultOCRParams( - feedChainSel uint64, - tokenInfo map[ccipocr3.UnknownEncodedAddress]pluginconfig.TokenInfo, -) CCIPOCRParams { - return CCIPOCRParams{ - OCRParameters: types.OCRParameters{ - DeltaProgress: internal.DeltaProgress, - DeltaResend: internal.DeltaResend, - DeltaInitial: internal.DeltaInitial, - DeltaRound: internal.DeltaRound, - DeltaGrace: internal.DeltaGrace, - DeltaCertifiedCommitRequest: internal.DeltaCertifiedCommitRequest, - DeltaStage: internal.DeltaStage, - Rmax: internal.Rmax, - MaxDurationQuery: internal.MaxDurationQuery, - MaxDurationObservation: internal.MaxDurationObservation, - MaxDurationShouldAcceptAttestedReport: internal.MaxDurationShouldAcceptAttestedReport, - MaxDurationShouldTransmitAcceptedReport: internal.MaxDurationShouldTransmitAcceptedReport, - }, - ExecuteOffChainConfig: pluginconfig.ExecuteOffchainConfig{ - BatchGasLimit: internal.BatchGasLimit, - RelativeBoostPerWaitHour: internal.RelativeBoostPerWaitHour, - InflightCacheExpiry: *config.MustNewDuration(internal.InflightCacheExpiry), - RootSnoozeTime: *config.MustNewDuration(internal.RootSnoozeTime), - MessageVisibilityInterval: *config.MustNewDuration(internal.FirstBlockAge), - BatchingStrategyID: internal.BatchingStrategyID, - }, - CommitOffChainConfig: pluginconfig.CommitOffchainConfig{ - RemoteGasPriceBatchWriteFrequency: *config.MustNewDuration(internal.RemoteGasPriceBatchWriteFrequency), - TokenPriceBatchWriteFrequency: *config.MustNewDuration(internal.TokenPriceBatchWriteFrequency), - TokenInfo: tokenInfo, - PriceFeedChainSelector: ccipocr3.ChainSelector(feedChainSel), - NewMsgScanBatchSize: merklemulti.MaxNumberTreeLeaves, - MaxReportTransmissionCheckAttempts: 5, - RMNEnabled: os.Getenv("ENABLE_RMN") == "true", // only enabled in manual test - RMNSignaturesTimeout: 30 * time.Minute, - MaxMerkleTreeSize: merklemulti.MaxNumberTreeLeaves, - SignObservationPrefix: "chainlink ccip 1.6 rmn observation", - }, - } -} diff --git a/deployment/ccip/changeset/jobspec.go b/deployment/ccip/changeset/jobspec.go deleted file mode 100644 index 04b658202ea..00000000000 --- a/deployment/ccip/changeset/jobspec.go +++ /dev/null @@ -1,24 +0,0 @@ -package changeset - -import ( - "github.com/pkg/errors" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" -) - -var _ deployment.ChangeSet[any] = CCIPCapabilityJobspec - -// CCIPCapabilityJobspec returns the job specs for the CCIP capability. -// The caller needs to propose these job specs to the offchain system. -func CCIPCapabilityJobspec(env deployment.Environment, _ any) (deployment.ChangesetOutput, error) { - js, err := NewCCIPJobSpecs(env.NodeIDs, env.Offchain) - if err != nil { - return deployment.ChangesetOutput{}, errors.Wrapf(err, "failed to create job specs") - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: nil, - JobSpecs: js, - }, nil -} diff --git a/deployment/ccip/changeset/prerequisites.go b/deployment/ccip/changeset/prerequisites.go deleted file mode 100644 index 34780809827..00000000000 --- a/deployment/ccip/changeset/prerequisites.go +++ /dev/null @@ -1,58 +0,0 @@ -package changeset - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" -) - -var ( - _ deployment.ChangeSet[DeployPrerequisiteConfig] = DeployPrerequisites -) - -// DeployPrerequisites deploys the pre-requisite contracts for CCIP -// pre-requisite contracts are the contracts which can be reused from previous versions of CCIP -// Or the contracts which are already deployed on the chain ( for example, tokens, feeds, etc) -// Caller should update the environment's address book with the returned addresses. -func DeployPrerequisites(env deployment.Environment, cfg DeployPrerequisiteConfig) (deployment.ChangesetOutput, error) { - err := cfg.Validate() - if err != nil { - return deployment.ChangesetOutput{}, errors.Wrapf(deployment.ErrInvalidConfig, "%v", err) - } - ab := deployment.NewMemoryAddressBook() - err = deployPrerequisiteChainContracts(env, ab, cfg.ChainSelectors, cfg.Opts...) - if err != nil { - env.Logger.Errorw("Failed to deploy prerequisite contracts", "err", err, "addressBook", ab) - return deployment.ChangesetOutput{ - AddressBook: ab, - }, fmt.Errorf("failed to deploy prerequisite contracts: %w", err) - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: ab, - JobSpecs: nil, - }, nil -} - -type DeployPrerequisiteConfig struct { - ChainSelectors []uint64 - Opts []PrerequisiteOpt - // TODO handle tokens and feeds in prerequisite config - Tokens map[TokenSymbol]common.Address - Feeds map[TokenSymbol]common.Address -} - -func (c DeployPrerequisiteConfig) Validate() error { - mapAllChainSelectors := make(map[uint64]struct{}) - for _, cs := range c.ChainSelectors { - mapAllChainSelectors[cs] = struct{}{} - if err := deployment.IsValidChainSelector(cs); err != nil { - return fmt.Errorf("invalid chain selector: %d - %w", cs, err) - } - } - return nil -} diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index add99386a31..fe7b7008982 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -48,6 +48,37 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface" ) +var ( + MockRMN deployment.ContractType = "MockRMN" + RMNRemote deployment.ContractType = "RMNRemote" + LinkToken deployment.ContractType = "LinkToken" + ARMProxy deployment.ContractType = "ARMProxy" + WETH9 deployment.ContractType = "WETH9" + Router deployment.ContractType = "Router" + CommitStore deployment.ContractType = "CommitStore" + TokenAdminRegistry deployment.ContractType = "TokenAdminRegistry" + RegistryModule deployment.ContractType = "RegistryModuleOwnerCustom" + NonceManager deployment.ContractType = "NonceManager" + FeeQuoter deployment.ContractType = "FeeQuoter" + CCIPHome deployment.ContractType = "CCIPHome" + CCIPConfig deployment.ContractType = "CCIPConfig" + RMNHome deployment.ContractType = "RMNHome" + OnRamp deployment.ContractType = "OnRamp" + OffRamp deployment.ContractType = "OffRamp" + CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" + PriceFeed deployment.ContractType = "PriceFeed" + // Note test router maps to a regular router contract. + TestRouter deployment.ContractType = "TestRouter" + Multicall3 deployment.ContractType = "Multicall3" + CCIPReceiver deployment.ContractType = "CCIPReceiver" + BurnMintToken deployment.ContractType = "BurnMintToken" + BurnMintTokenPool deployment.ContractType = "BurnMintTokenPool" + USDCToken deployment.ContractType = "USDCToken" + USDCMockTransmitter deployment.ContractType = "USDCMockTransmitter" + USDCTokenMessenger deployment.ContractType = "USDCTokenMessenger" + USDCTokenPool deployment.ContractType = "USDCTokenPool" +) + // CCIPChainState holds a Go binding for all the currently deployed CCIP contracts // on a chain. If a binding is nil, it means here is no such contract on the chain. type CCIPChainState struct { diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index ea1cea2c790..cd135cf7975 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -21,7 +21,6 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" @@ -98,9 +97,9 @@ type DeployedEnv struct { func (e *DeployedEnv) SetupJobs(t *testing.T) { ctx := testcontext.Get(t) - jbs, err := NewCCIPJobSpecs(e.Env.NodeIDs, e.Env.Offchain) + out, err := CCIPCapabilityJobspec(e.Env, struct{}{}) require.NoError(t, err) - for nodeID, jobs := range jbs { + for nodeID, jobs := range out.JobSpecs { for _, job := range jobs { // Note these auto-accept _, err := e.Env.Offchain.ProposeJob(ctx, @@ -137,7 +136,7 @@ func DeployTestContracts(t *testing.T, linkPrice *big.Int, wethPrice *big.Int, ) deployment.CapabilityRegistryConfig { - capReg, err := DeployCapReg(lggr, + capReg, err := deployCapReg(lggr, // deploying cap reg for the first time on a blank chain state CCIPOnChainState{ Chains: make(map[uint64]CCIPChainState), @@ -268,16 +267,15 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, var err error e := NewMemoryEnvironment(t, lggr, numChains, numNodes, MockLinkPrice, MockWethPrice) allChains := e.Env.AllChainSelectors() - cfg := commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, c := range e.Env.AllChainSelectors() { - mcmsCfg[c] = cfg + mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.Env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } } var usdcChains []uint64 if tCfg != nil && tCfg.IsUSDC { @@ -311,56 +309,58 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, state, err := LoadOnchainState(e.Env) require.NoError(t, err) - tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) - ocrParams := make(map[uint64]CCIPOCRParams) - usdcCCTPConfig := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) - timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) + // Assert USDC set up as expected. for _, chain := range usdcChains { require.NotNil(t, state.Chains[chain].MockUSDCTokenMessenger) require.NotNil(t, state.Chains[chain].MockUSDCTransmitter) require.NotNil(t, state.Chains[chain].USDCTokenPool) - usdcCCTPConfig[cciptypes.ChainSelector(chain)] = pluginconfig.USDCCCTPTokenConfig{ - SourcePoolAddress: state.Chains[chain].USDCTokenPool.Address().String(), - SourceMessageTransmitterAddr: state.Chains[chain].MockUSDCTransmitter.Address().String(), - } } + // Assert link present require.NotNil(t, state.Chains[e.FeedChainSel].LinkToken) require.NotNil(t, state.Chains[e.FeedChainSel].Weth9) - var usdcCfg USDCAttestationConfig + + tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + var tokenDataProviders []pluginconfig.TokenDataObserverConfig if len(usdcChains) > 0 { server := mockAttestationResponse() endpoint := server.URL - usdcCfg = USDCAttestationConfig{ - API: endpoint, - APITimeout: commonconfig.MustNewDuration(time.Second), - APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), - } t.Cleanup(func() { server.Close() }) - } - + cctpContracts := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) + for _, usdcChain := range usdcChains { + cctpContracts[cciptypes.ChainSelector(usdcChain)] = pluginconfig.USDCCCTPTokenConfig{ + SourcePoolAddress: state.Chains[usdcChain].USDCTokenPool.Address().String(), + SourceMessageTransmitterAddr: state.Chains[usdcChain].MockUSDCTransmitter.Address().String(), + } + } + tokenDataProviders = append(tokenDataProviders, pluginconfig.TokenDataObserverConfig{ + Type: pluginconfig.USDCCCTPHandlerType, + Version: "1.0", + USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ + Tokens: cctpContracts, + AttestationAPI: endpoint, + AttestationAPITimeout: commonconfig.MustNewDuration(time.Second), + AttestationAPIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + }}) + } + // Build the per chain config. + chainConfigs := make(map[uint64]CCIPOCRParams) + timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - ocrParams[chain] = DefaultOCRParams(e.FeedChainSel, tokenInfo) + chainConfigs[chain] = DefaultOCRParams(e.FeedChainSel, tokenInfo, tokenDataProviders) } // Deploy second set of changesets to deploy and configure the CCIP contracts. e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), Config: NewChainsConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainsToDeploy: allChains, - TokenConfig: tokenConfig, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - USDCConfig: USDCConfig{ - EnabledChains: usdcChains, - USDCAttestationConfig: usdcCfg, - CCTPTokenConfig: usdcCCTPConfig, - }, - OCRParams: ocrParams, + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + ChainConfigByChain: chainConfigs, }, }, { @@ -489,6 +489,20 @@ func TestSendRequest( testRouter bool, evm2AnyMessage router.ClientEVM2AnyMessage, ) (msgSentEvent *onramp.OnRampCCIPMessageSent) { + msgSentEvent, err := DoSendRequest(t, e, state, src, dest, testRouter, evm2AnyMessage) + require.NoError(t, err) + return msgSentEvent +} + +// DoSendRequest similar to TestSendRequest but returns an error. +func DoSendRequest( + t *testing.T, + e deployment.Environment, + state CCIPOnChainState, + src, dest uint64, + testRouter bool, + evm2AnyMessage router.ClientEVM2AnyMessage, +) (*onramp.OnRampCCIPMessageSent, error) { t.Logf("Sending CCIP request from chain selector %d to chain selector %d", src, dest) tx, blockNum, err := CCIPSendRequest( @@ -498,13 +512,19 @@ func TestSendRequest( testRouter, evm2AnyMessage, ) - require.NoError(t, err) + if err != nil { + return nil, err + } + it, err := state.Chains[src].OnRamp.FilterCCIPMessageSent(&bind.FilterOpts{ Start: blockNum, End: &blockNum, Context: context.Background(), }, []uint64{dest}, []uint64{}) - require.NoError(t, err) + if err != nil { + return nil, err + } + require.True(t, it.Next()) t.Logf("CCIP message (id %x) sent from chain selector %d to chain selector %d tx %s seqNum %d nonce %d sender %s", it.Event.Message.Header.MessageId[:], @@ -515,7 +535,7 @@ func TestSendRequest( it.Event.Message.Header.Nonce, it.Event.Message.Sender.String(), ) - return it.Event + return it.Event, nil } // MakeEVMExtraArgsV2 creates the extra args for the EVM2Any message that is destined @@ -543,6 +563,16 @@ func MakeEVMExtraArgsV2(gasLimit uint64, allowOOO bool) []byte { return extraArgs } +func AddLaneWithDefaultPricesAndFeeQuoterConfig(e deployment.Environment, state CCIPOnChainState, from, to uint64, isTestRouter bool) error { + cfg := LaneConfig{ + SourceSelector: from, + DestSelector: to, + InitialPricesBySource: DefaultInitialPrices, + FeeQuoterDestChain: DefaultFeeQuoterDestChainConfig(), + } + return addLane(e, state, cfg, isTestRouter) +} + // AddLanesForAll adds densely connected lanes for all chains in the environment so that each chain // is connected to every other chain except itself. func AddLanesForAll(e deployment.Environment, state CCIPOnChainState) error { @@ -566,11 +596,11 @@ func ToPackedFee(execFee, daFee *big.Int) *big.Int { const ( // MockLinkAggregatorDescription This is the description of the MockV3Aggregator.sol contract - // nolint:lll + //nolint:lll // https://github.com/smartcontractkit/chainlink/blob/a348b98e90527520049c580000a86fb8ceff7fa7/contracts/src/v0.8/tests/MockV3Aggregator.sol#L76-L76 MockLinkAggregatorDescription = "v0.8/tests/MockV3Aggregator.sol" // MockWETHAggregatorDescription WETH use description from MockETHUSDAggregator.sol - // nolint:lll + //nolint:lll // https://github.com/smartcontractkit/chainlink/blob/a348b98e90527520049c580000a86fb8ceff7fa7/contracts/src/v0.8/automation/testhelpers/MockETHUSDAggregator.sol#L19-L19 MockWETHAggregatorDescription = "MockETHUSDAggregator" ) @@ -1107,6 +1137,7 @@ func TransferAndWaitForSuccess( receiver common.Address, data []byte, expectedStatus int, + extraArgs []byte, ) { identifier := SourceDestPair{ SourceChainSelector: sourceChain, @@ -1127,7 +1158,7 @@ func TransferAndWaitForSuccess( Data: data, TokenAmounts: tokens, FeeToken: common.HexToAddress("0x0"), - ExtraArgs: nil, + ExtraArgs: extraArgs, }) expectedSeqNum[identifier] = msgSentEvent.SequenceNumber expectedSeqNumExec[identifier] = []uint64{msgSentEvent.SequenceNumber} diff --git a/deployment/ccip/changeset/token_info.go b/deployment/ccip/changeset/token_info.go index e9657544a01..5bd6b2ed66e 100644 --- a/deployment/ccip/changeset/token_info.go +++ b/deployment/ccip/changeset/token_info.go @@ -11,6 +11,18 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" ) +type TokenSymbol string + +const ( + LinkSymbol TokenSymbol = "LINK" + WethSymbol TokenSymbol = "WETH" + USDCSymbol TokenSymbol = "USDC" + USDCName string = "USD Coin" + LinkDecimals = 18 + WethDecimals = 18 + UsdcDecimals = 6 +) + var ( TestDeviationPPB = ccipocr3.NewBigIntFromInt64(1e9) ) diff --git a/deployment/common/changeset/accept_ownership.go b/deployment/common/changeset/accept_ownership.go index 7f89e5cb75f..79aa876eabb 100644 --- a/deployment/common/changeset/accept_ownership.go +++ b/deployment/common/changeset/accept_ownership.go @@ -12,6 +12,7 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" ) @@ -22,8 +23,8 @@ type OwnershipAcceptor interface { } type AcceptOwnershipConfig struct { - // TimelocksPerChain is a mapping from chain selector to the timelock contract address on that chain. - TimelocksPerChain map[uint64]common.Address + // OwnersPerChain is a mapping from chain selector to the owner contract address on that chain. + OwnersPerChain map[uint64]common.Address // ProposerMCMSes is a mapping from chain selector to the proposer MCMS contract on that chain. ProposerMCMSes map[uint64]*gethwrappers.ManyChainMultiSig @@ -39,11 +40,11 @@ type AcceptOwnershipConfig struct { } func (a AcceptOwnershipConfig) Validate() error { - // check that we have timelocks and proposer mcmses for the chains + // check that we have owners and proposer mcmses for the chains // in the Contracts field. for chainSelector := range a.Contracts { - if _, ok := a.TimelocksPerChain[chainSelector]; !ok { - return fmt.Errorf("missing timelock for chain %d", chainSelector) + if _, ok := a.OwnersPerChain[chainSelector]; !ok { + return fmt.Errorf("missing owner for chain %d", chainSelector) } if _, ok := a.ProposerMCMSes[chainSelector]; !ok { return fmt.Errorf("missing proposer MCMS for chain %d", chainSelector) @@ -88,7 +89,7 @@ func NewAcceptOwnershipChangeset( } proposal, err := proposalutils.BuildProposalFromBatches( - cfg.TimelocksPerChain, + cfg.OwnersPerChain, cfg.ProposerMCMSes, batches, "Accept ownership of contracts", diff --git a/deployment/common/changeset/accept_ownership_test.go b/deployment/common/changeset/accept_ownership_test.go index 27feb6389bc..fc2c296f2d8 100644 --- a/deployment/common/changeset/accept_ownership_test.go +++ b/deployment/common/changeset/accept_ownership_test.go @@ -6,8 +6,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink/deployment/common/changeset" ) func TestAcceptOwnershipConfig_Validate(t *testing.T) { @@ -19,7 +20,7 @@ func TestAcceptOwnershipConfig_Validate(t *testing.T) { { name: "valid config", config: changeset.AcceptOwnershipConfig{ - TimelocksPerChain: map[uint64]common.Address{ + OwnersPerChain: map[uint64]common.Address{ 1: common.HexToAddress("0x1"), }, ProposerMCMSes: map[uint64]*gethwrappers.ManyChainMultiSig{ @@ -35,7 +36,7 @@ func TestAcceptOwnershipConfig_Validate(t *testing.T) { { name: "missing timelock", config: changeset.AcceptOwnershipConfig{ - TimelocksPerChain: map[uint64]common.Address{}, + OwnersPerChain: map[uint64]common.Address{}, ProposerMCMSes: map[uint64]*gethwrappers.ManyChainMultiSig{ 1: {}, }, @@ -49,7 +50,7 @@ func TestAcceptOwnershipConfig_Validate(t *testing.T) { { name: "missing proposer MCMS", config: changeset.AcceptOwnershipConfig{ - TimelocksPerChain: map[uint64]common.Address{ + OwnersPerChain: map[uint64]common.Address{ 1: common.HexToAddress("0x1"), }, ProposerMCMSes: map[uint64]*gethwrappers.ManyChainMultiSig{}, diff --git a/deployment/common/changeset/transfer_ownership.go b/deployment/common/changeset/transfer_ownership.go index a0085fb61ca..36fe2cbed78 100644 --- a/deployment/common/changeset/transfer_ownership.go +++ b/deployment/common/changeset/transfer_ownership.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/smartcontractkit/chainlink/deployment" ) @@ -15,18 +16,18 @@ type OwnershipTransferrer interface { } type TransferOwnershipConfig struct { - // TimelocksPerChain is a mapping from chain selector to the timelock contract address on that chain. - TimelocksPerChain map[uint64]common.Address + // OwnersPerChain is a mapping from chain selector to the owner's contract address on that chain. + OwnersPerChain map[uint64]common.Address // Contracts is a mapping from chain selector to the ownership transferrers on that chain. Contracts map[uint64][]OwnershipTransferrer } func (t TransferOwnershipConfig) Validate() error { - // check that we have timelocks for the chains in the Contracts field. + // check that we have owners for the chains in the Contracts field. for chainSelector := range t.Contracts { - if _, ok := t.TimelocksPerChain[chainSelector]; !ok { - return fmt.Errorf("missing timelock for chain %d", chainSelector) + if _, ok := t.OwnersPerChain[chainSelector]; !ok { + return fmt.Errorf("missing owners for chain %d", chainSelector) } } @@ -36,8 +37,8 @@ func (t TransferOwnershipConfig) Validate() error { var _ deployment.ChangeSet[TransferOwnershipConfig] = NewTransferOwnershipChangeset // NewTransferOwnershipChangeset creates a changeset that transfers ownership of all the -// contracts in the provided configuration to the the appropriate timelock on that chain. -// If the owner is already the timelock contract, no transaction is sent. +// contracts in the provided configuration to correct owner on that chain. +// If the owner is already the provided address, no transaction is sent. func NewTransferOwnershipChangeset( e deployment.Environment, cfg TransferOwnershipConfig, @@ -47,14 +48,14 @@ func NewTransferOwnershipChangeset( } for chainSelector, contracts := range cfg.Contracts { - timelock := cfg.TimelocksPerChain[chainSelector] + ownerAddress := cfg.OwnersPerChain[chainSelector] for _, contract := range contracts { owner, err := contract.Owner(nil) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to get owner of contract %T: %v", contract, err) } - if owner != timelock { - tx, err := contract.TransferOwnership(e.Chains[chainSelector].DeployerKey, timelock) + if owner != ownerAddress { + tx, err := contract.TransferOwnership(e.Chains[chainSelector].DeployerKey, ownerAddress) _, err = deployment.ConfirmIfNoError(e.Chains[chainSelector], tx, err) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to transfer ownership of contract %T: %v", contract, err) diff --git a/deployment/environment/clo/don_nodeset_test.go b/deployment/environment/clo/don_nodeset_test.go index b576e95835a..fab9a81690b 100644 --- a/deployment/environment/clo/don_nodeset_test.go +++ b/deployment/environment/clo/don_nodeset_test.go @@ -61,7 +61,7 @@ func TestGenerateNopNodesData(t *testing.T) { require.NotEmpty(t, ksNops) b, err := json.MarshalIndent(ksNops, "", " ") require.NoError(t, err) - require.NoError(t, os.WriteFile("testdata/keystone_nops.json", b, 0644)) // nolint: gosec + require.NoError(t, os.WriteFile("testdata/keystone_nops.json", b, 0644)) //nolint:gosec } keystoneNops := loadTestNops(t, "testdata/keystone_nops.json") diff --git a/deployment/go.mod b/deployment/go.mod index cd11ea86e76..f1584f39072 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -404,7 +404,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.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 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 // indirect diff --git a/deployment/go.sum b/deployment/go.sum index 7d25c472c9c..f40e5b5f21f 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1421,8 +1421,8 @@ github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6An 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.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +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= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749/go.mod h1:nkIegLHodyrrZguxkYEHcNw2vAXv8H8xlCoLzwylcL0= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= diff --git a/deployment/keystone/capability_registry_deployer.go b/deployment/keystone/capability_registry_deployer.go index a4648c27905..11c85453927 100644 --- a/deployment/keystone/capability_registry_deployer.go +++ b/deployment/keystone/capability_registry_deployer.go @@ -19,8 +19,12 @@ type CapabilitiesRegistryDeployer struct { contract *capabilities_registry.CapabilitiesRegistry } -func NewCapabilitiesRegistryDeployer(lggr logger.Logger) *CapabilitiesRegistryDeployer { - return &CapabilitiesRegistryDeployer{lggr: lggr} +func NewCapabilitiesRegistryDeployer() (*CapabilitiesRegistryDeployer, error) { + lggr, err := logger.New() + if err != nil { + return nil, err + } + return &CapabilitiesRegistryDeployer{lggr: lggr}, nil } func (c *CapabilitiesRegistryDeployer) Contract() *capabilities_registry.CapabilitiesRegistry { diff --git a/deployment/keystone/changeset/accept_ownership.go b/deployment/keystone/changeset/accept_ownership.go new file mode 100644 index 00000000000..8a4f3c60c53 --- /dev/null +++ b/deployment/keystone/changeset/accept_ownership.go @@ -0,0 +1,89 @@ +package changeset + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + + ccipowner "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" +) + +func toOwnershipAcceptors[T changeset.OwnershipAcceptor](items []T) []changeset.OwnershipAcceptor { + ownershipAcceptors := make([]changeset.OwnershipAcceptor, len(items)) + for i, item := range items { + ownershipAcceptors[i] = item + } + return ownershipAcceptors +} + +type AcceptAllOwnershipRequest struct { + ChainSelector uint64 + MinDelay time.Duration +} + +var _ deployment.ChangeSet[*AcceptAllOwnershipRequest] = AcceptAllOwnershipsProposal + +// AcceptAllOwnershipsProposal creates a MCMS proposal to call accept ownership on all the Keystone contracts in the address book. +func AcceptAllOwnershipsProposal(e deployment.Environment, req *AcceptAllOwnershipRequest) (deployment.ChangesetOutput, error) { + chainSelector := req.ChainSelector + minDelay := req.MinDelay + chain := e.Chains[chainSelector] + addrBook := e.ExistingAddresses + + // Fetch contracts from the address book. + timelocks, err := timelocksFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, err + } + capRegs, err := capRegistriesFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, err + } + ocr3, err := ocr3FromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, err + } + forwarders, err := forwardersFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, err + } + consumers, err := feedsConsumersFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, err + } + mcmsProposers, err := proposersFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + // Initialize the OwnershipAcceptors slice + var ownershipAcceptors []changeset.OwnershipAcceptor + + // Append all contracts + ownershipAcceptors = append(ownershipAcceptors, toOwnershipAcceptors(capRegs)...) + ownershipAcceptors = append(ownershipAcceptors, toOwnershipAcceptors(ocr3)...) + ownershipAcceptors = append(ownershipAcceptors, toOwnershipAcceptors(forwarders)...) + ownershipAcceptors = append(ownershipAcceptors, toOwnershipAcceptors(consumers)...) + + // Construct the configuration + cfg := changeset.AcceptOwnershipConfig{ + OwnersPerChain: map[uint64]common.Address{ + // Assuming there is only one timelock per chain. + chainSelector: timelocks[0].Address(), + }, + ProposerMCMSes: map[uint64]*ccipowner.ManyChainMultiSig{ + // Assuming there is only one MCMS proposer per chain. + chainSelector: mcmsProposers[0], + }, + Contracts: map[uint64][]changeset.OwnershipAcceptor{ + chainSelector: ownershipAcceptors, + }, + MinDelay: minDelay, + } + + // Create and return the changeset + return changeset.NewAcceptOwnershipChangeset(e, cfg) +} diff --git a/deployment/keystone/changeset/accept_ownership_test.go b/deployment/keystone/changeset/accept_ownership_test.go new file mode 100644 index 00000000000..996ff08c149 --- /dev/null +++ b/deployment/keystone/changeset/accept_ownership_test.go @@ -0,0 +1,86 @@ +package changeset_test + +import ( + "math/big" + "testing" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" +) + +func TestAcceptAllOwnership(t *testing.T) { + t.Parallel() + lggr := logger.Test(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + registrySel := env.AllChainSelectors()[0] + chCapReg, err := changeset.DeployCapabilityRegistry(env, registrySel) + require.NoError(t, err) + require.NotNil(t, chCapReg) + err = env.ExistingAddresses.Merge(chCapReg.AddressBook) + require.NoError(t, err) + + chOcr3, err := changeset.DeployOCR3(env, registrySel) + require.NoError(t, err) + require.NotNil(t, chOcr3) + err = env.ExistingAddresses.Merge(chOcr3.AddressBook) + require.NoError(t, err) + + chForwarder, err := changeset.DeployForwarder(env, registrySel) + require.NoError(t, err) + require.NotNil(t, chForwarder) + err = env.ExistingAddresses.Merge(chForwarder.AddressBook) + require.NoError(t, err) + + chConsumer, err := changeset.DeployFeedsConsumer(env, &changeset.DeployFeedsConsumerRequest{ + ChainSelector: registrySel, + }) + require.NoError(t, err) + require.NotNil(t, chConsumer) + err = env.ExistingAddresses.Merge(chConsumer.AddressBook) + require.NoError(t, err) + + chMcms, err := commonchangeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ + registrySel: { + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + }, + }) + err = env.ExistingAddresses.Merge(chMcms.AddressBook) + require.NoError(t, err) + + require.NoError(t, err) + require.NotNil(t, chMcms) + + resp, err := changeset.TransferAllOwnership(env, &changeset.TransferAllOwnershipRequest{ + ChainSelector: registrySel, + }) + require.NoError(t, err) + require.NotNil(t, resp) + + // Test the changeset + output, err := changeset.AcceptAllOwnershipsProposal(env, &changeset.AcceptAllOwnershipRequest{ + ChainSelector: registrySel, + MinDelay: time.Duration(0), + }) + require.NoError(t, err) + require.NotNil(t, output) + require.Len(t, output.Proposals, 1) + proposal := output.Proposals[0] + require.Len(t, proposal.Transactions, 1) + txs := proposal.Transactions[0] + require.Len(t, txs.Batch, 4) +} diff --git a/deployment/keystone/changeset/addrbook_utils.go b/deployment/keystone/changeset/addrbook_utils.go new file mode 100644 index 00000000000..4b6d6d151b1 --- /dev/null +++ b/deployment/keystone/changeset/addrbook_utils.go @@ -0,0 +1,112 @@ +package changeset + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + ccipowner "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/keystone" + capReg "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/feeds_consumer" + keystoneForwarder "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" + ocr3Capability "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/ocr3_capability" +) + +// contractConstructor is a function type that takes an address and a client, +// returning the contract instance and an error. +type contractConstructor[T any] func(address common.Address, client bind.ContractBackend) (*T, error) + +// getContractsFromAddrBook retrieves a list of contract instances of a specified type from the address book. +// It uses the provided constructor to initialize matching contracts for the given chain. +func getContractsFromAddrBook[T any]( + addrBook deployment.AddressBook, + chain deployment.Chain, + desiredType deployment.ContractType, + constructor contractConstructor[T], +) ([]*T, error) { + chainAddresses, err := addrBook.AddressesForChain(chain.Selector) + if err != nil { + return nil, fmt.Errorf("failed to get addresses for chain %d: %w", chain.Selector, err) + } + + var contracts []*T + for addr, typeAndVersion := range chainAddresses { + if typeAndVersion.Type == desiredType { + address := common.HexToAddress(addr) + contractInstance, err := constructor(address, chain.Client) + if err != nil { + return nil, fmt.Errorf("failed to construct %s at %s: %w", desiredType, addr, err) + } + contracts = append(contracts, contractInstance) + } + } + + if len(contracts) == 0 { + return nil, fmt.Errorf("no %s found for chain %d", desiredType, chain.Selector) + } + + return contracts, nil +} + +// capRegistriesFromAddrBook retrieves CapabilitiesRegistry contracts for the given chain. +func capRegistriesFromAddrBook(addrBook deployment.AddressBook, chain deployment.Chain) ([]*capReg.CapabilitiesRegistry, error) { + return getContractsFromAddrBook[capReg.CapabilitiesRegistry]( + addrBook, + chain, + keystone.CapabilitiesRegistry, + capReg.NewCapabilitiesRegistry, + ) +} + +// ocr3FromAddrBook retrieves OCR3Capability contracts for the given chain. +func ocr3FromAddrBook(addrBook deployment.AddressBook, chain deployment.Chain) ([]*ocr3Capability.OCR3Capability, error) { + return getContractsFromAddrBook[ocr3Capability.OCR3Capability]( + addrBook, + chain, + keystone.OCR3Capability, + ocr3Capability.NewOCR3Capability, + ) +} + +// forwardersFromAddrBook retrieves KeystoneForwarder contracts for the given chain. +func forwardersFromAddrBook(addrBook deployment.AddressBook, chain deployment.Chain) ([]*keystoneForwarder.KeystoneForwarder, error) { + return getContractsFromAddrBook[keystoneForwarder.KeystoneForwarder]( + addrBook, + chain, + keystone.KeystoneForwarder, + keystoneForwarder.NewKeystoneForwarder, + ) +} + +// feedsConsumersFromAddrBook retrieves FeedsConsumer contracts for the given chain. +func feedsConsumersFromAddrBook(addrBook deployment.AddressBook, chain deployment.Chain) ([]*feeds_consumer.KeystoneFeedsConsumer, error) { + return getContractsFromAddrBook[feeds_consumer.KeystoneFeedsConsumer]( + addrBook, + chain, + keystone.FeedConsumer, + feeds_consumer.NewKeystoneFeedsConsumer, + ) +} + +// proposersFromAddrBook retrieves ManyChainMultiSig proposer contracts for the given chain. +func proposersFromAddrBook(addrBook deployment.AddressBook, chain deployment.Chain) ([]*ccipowner.ManyChainMultiSig, error) { + return getContractsFromAddrBook[ccipowner.ManyChainMultiSig]( + addrBook, + chain, + keystone.ProposerManyChainMultiSig, + ccipowner.NewManyChainMultiSig, + ) +} + +// timelocksFromAddrBook retrieves RBACTimelock contracts for the given chain. +func timelocksFromAddrBook(addrBook deployment.AddressBook, chain deployment.Chain) ([]*ccipowner.RBACTimelock, error) { + return getContractsFromAddrBook[ccipowner.RBACTimelock]( + addrBook, + chain, + keystone.RBACTimelock, + ccipowner.NewRBACTimelock, + ) +} diff --git a/deployment/keystone/changeset/deploy_consumer.go b/deployment/keystone/changeset/deploy_consumer.go new file mode 100644 index 00000000000..fc7992e2a7d --- /dev/null +++ b/deployment/keystone/changeset/deploy_consumer.go @@ -0,0 +1,31 @@ +package changeset + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/deployment" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" +) + +type DeployFeedsConsumerRequest struct { + ChainSelector uint64 +} + +var _ deployment.ChangeSet[*DeployFeedsConsumerRequest] = DeployFeedsConsumer + +// DeployFeedsConsumer deploys the FeedsConsumer contract to the chain with the given chainSelector. +func DeployFeedsConsumer(env deployment.Environment, req *DeployFeedsConsumerRequest) (deployment.ChangesetOutput, error) { + chainSelector := req.ChainSelector + lggr := env.Logger + chain, ok := env.Chains[chainSelector] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("chain not found in environment") + } + ab := deployment.NewMemoryAddressBook() + deployResp, err := kslib.DeployFeedsConsumer(chain, ab) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to deploy FeedsConsumer: %w", err) + } + lggr.Infof("Deployed %s chain selector %d addr %s", deployResp.Tv.String(), chain.Selector, deployResp.Address.String()) + return deployment.ChangesetOutput{AddressBook: ab}, nil +} diff --git a/deployment/keystone/changeset/deploy_consumer_test.go b/deployment/keystone/changeset/deploy_consumer_test.go new file mode 100644 index 00000000000..9a1e8f57da7 --- /dev/null +++ b/deployment/keystone/changeset/deploy_consumer_test.go @@ -0,0 +1,40 @@ +package changeset_test + +import ( + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" +) + +func TestDeployFeedsConsumer(t *testing.T) { + t.Parallel() + lggr := logger.Test(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + + registrySel := env.AllChainSelectors()[0] + resp, err := changeset.DeployFeedsConsumer(env, &changeset.DeployFeedsConsumerRequest{ + ChainSelector: registrySel, + }) + require.NoError(t, err) + require.NotNil(t, resp) + // feeds consumer should be deployed on chain 0 + addrs, err := resp.AddressBook.AddressesForChain(registrySel) + require.NoError(t, err) + require.Len(t, addrs, 1) + + // no feeds consumer registry on chain 1 + require.NotEqual(t, registrySel, env.AllChainSelectors()[1]) + oaddrs, _ := resp.AddressBook.AddressesForChain(env.AllChainSelectors()[1]) + require.Len(t, oaddrs, 0) +} diff --git a/deployment/keystone/changeset/deploy_forwarder.go b/deployment/keystone/changeset/deploy_forwarder.go index 55ab0dcd86d..2dc160dcf4c 100644 --- a/deployment/keystone/changeset/deploy_forwarder.go +++ b/deployment/keystone/changeset/deploy_forwarder.go @@ -22,10 +22,11 @@ func DeployForwarder(env deployment.Environment, registryChainSel uint64) (deplo ab := deployment.NewMemoryAddressBook() for _, chain := range env.Chains { lggr.Infow("deploying forwarder", "chainSelector", chain.Selector) - err := kslib.DeployForwarder(lggr, chain, ab) + forwarderResp, err := kslib.DeployForwarder(chain, ab) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to deploy KeystoneForwarder to chain selector %d: %w", chain.Selector, err) } + lggr.Infof("Deployed %s chain selector %d addr %s", forwarderResp.Tv.String(), chain.Selector, forwarderResp.Address.String()) } return deployment.ChangesetOutput{AddressBook: ab}, nil diff --git a/deployment/keystone/changeset/deploy_ocr3.go b/deployment/keystone/changeset/deploy_ocr3.go index 6684d8e046b..a88fe4afa62 100644 --- a/deployment/keystone/changeset/deploy_ocr3.go +++ b/deployment/keystone/changeset/deploy_ocr3.go @@ -4,11 +4,13 @@ import ( "fmt" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" ) func DeployOCR3(env deployment.Environment, config interface{}) (deployment.ChangesetOutput, error) { + lggr := env.Logger registryChainSel, ok := config.(uint64) if !ok { return deployment.ChangesetOutput{}, deployment.ErrInvalidConfig @@ -19,10 +21,11 @@ func DeployOCR3(env deployment.Environment, config interface{}) (deployment.Chan if !ok { return deployment.ChangesetOutput{}, fmt.Errorf("chain not found in environment") } - err := kslib.DeployOCR3(env.Logger, c, ab) + ocr3Resp, err := kslib.DeployOCR3(c, ab) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to deploy OCR3Capability: %w", err) } + lggr.Infof("Deployed %s chain selector %d addr %s", ocr3Resp.Tv.String(), c.Selector, ocr3Resp.Address.String()) return deployment.ChangesetOutput{AddressBook: ab}, nil } diff --git a/deployment/keystone/changeset/deploy_registry.go b/deployment/keystone/changeset/deploy_registry.go index e9b142812d7..d07e2728282 100644 --- a/deployment/keystone/changeset/deploy_registry.go +++ b/deployment/keystone/changeset/deploy_registry.go @@ -10,14 +10,17 @@ import ( var _ deployment.ChangeSet[uint64] = DeployCapabilityRegistry func DeployCapabilityRegistry(env deployment.Environment, registrySelector uint64) (deployment.ChangesetOutput, error) { + lggr := env.Logger chain, ok := env.Chains[registrySelector] if !ok { return deployment.ChangesetOutput{}, fmt.Errorf("chain not found in environment") } ab := deployment.NewMemoryAddressBook() - err := kslib.DeployCapabilitiesRegistry(env.Logger, chain, ab) + capabilitiesRegistryResp, err := kslib.DeployCapabilitiesRegistry(chain, ab) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to deploy CapabilitiesRegistry: %w", err) } + lggr.Infof("Deployed %s chain selector %d addr %s", capabilitiesRegistryResp.Tv.String(), chain.Selector, capabilitiesRegistryResp.Address.String()) + return deployment.ChangesetOutput{AddressBook: ab}, nil } diff --git a/deployment/keystone/changeset/internal/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go index cea20fd327d..7c0ab557a83 100644 --- a/deployment/keystone/changeset/internal/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -13,6 +13,7 @@ import ( capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/environment/memory" @@ -43,7 +44,7 @@ type SetupTestRegistryResponse struct { func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryRequest) *SetupTestRegistryResponse { chain := testChain(t) // deploy the registry - registry := deployCapReg(t, lggr, chain) + registry := deployCapReg(t, chain) // convert req to nodeoperators nops := make([]kcr.CapabilitiesRegistryNodeOperator, 0) for nop := range req.NopToNodes { @@ -101,9 +102,10 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR } } -func deployCapReg(t *testing.T, lggr logger.Logger, chain deployment.Chain) *kcr.CapabilitiesRegistry { - capabilitiesRegistryDeployer := kslib.NewCapabilitiesRegistryDeployer(lggr) - _, err := capabilitiesRegistryDeployer.Deploy(kslib.DeployRequest{Chain: chain}) +func deployCapReg(t *testing.T, chain deployment.Chain) *kcr.CapabilitiesRegistry { + capabilitiesRegistryDeployer, err := kslib.NewCapabilitiesRegistryDeployer() + require.NoError(t, err) + _, err = capabilitiesRegistryDeployer.Deploy(kslib.DeployRequest{Chain: chain}) require.NoError(t, err) return capabilitiesRegistryDeployer.Contract() } diff --git a/deployment/keystone/changeset/transfer_ownership.go b/deployment/keystone/changeset/transfer_ownership.go new file mode 100644 index 00000000000..73af5d5bdb2 --- /dev/null +++ b/deployment/keystone/changeset/transfer_ownership.go @@ -0,0 +1,84 @@ +package changeset + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" +) + +func toOwnershipTransferrer[T changeset.OwnershipTransferrer](items []T) []changeset.OwnershipTransferrer { + ownershipAcceptors := make([]changeset.OwnershipTransferrer, len(items)) + for i, item := range items { + ownershipAcceptors[i] = item + } + return ownershipAcceptors +} + +type TransferAllOwnershipRequest struct { + ChainSelector uint64 +} + +var _ deployment.ChangeSet[*TransferAllOwnershipRequest] = TransferAllOwnership + +// TransferAllOwnership transfers ownership of all Keystone contracts in the address book to the existing timelock. +func TransferAllOwnership(e deployment.Environment, req *TransferAllOwnershipRequest) (deployment.ChangesetOutput, error) { + chainSelector := req.ChainSelector + chain := e.Chains[chainSelector] + addrBook := e.ExistingAddresses + + // Fetch timelocks for the specified chain. + timelocks, err := timelocksFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch timelocks: %w", err) + } + if len(timelocks) == 0 { + return deployment.ChangesetOutput{}, fmt.Errorf("no timelocks found for chain %d", chainSelector) + } + + // Fetch contracts from the address book. + capRegs, err := capRegistriesFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch capabilities registries: %w", err) + } + + ocr3s, err := ocr3FromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch OCR3 capabilities: %w", err) + } + + forwarders, err := forwardersFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch forwarders: %w", err) + } + + consumers, err := feedsConsumersFromAddrBook(addrBook, chain) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch feeds consumers: %w", err) + } + + // Initialize the Contracts slice + var ownershipTransferrers []changeset.OwnershipTransferrer + + // Append all contracts + ownershipTransferrers = append(ownershipTransferrers, toOwnershipTransferrer(capRegs)...) + ownershipTransferrers = append(ownershipTransferrers, toOwnershipTransferrer(ocr3s)...) + ownershipTransferrers = append(ownershipTransferrers, toOwnershipTransferrer(forwarders)...) + ownershipTransferrers = append(ownershipTransferrers, toOwnershipTransferrer(consumers)...) + + // Construct the configuration + cfg := changeset.TransferOwnershipConfig{ + OwnersPerChain: map[uint64]common.Address{ + // Assuming there is only one timelock per chain. + chainSelector: timelocks[0].Address(), + }, + Contracts: map[uint64][]changeset.OwnershipTransferrer{ + chainSelector: ownershipTransferrers, + }, + } + + // Create and return the changeset + return changeset.NewTransferOwnershipChangeset(e, cfg) +} diff --git a/deployment/keystone/changeset/transfer_ownership_test.go b/deployment/keystone/changeset/transfer_ownership_test.go new file mode 100644 index 00000000000..dc5630076bd --- /dev/null +++ b/deployment/keystone/changeset/transfer_ownership_test.go @@ -0,0 +1,72 @@ +package changeset_test + +import ( + "math/big" + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" +) + +func TestTransferAllOwnership(t *testing.T) { + t.Parallel() + lggr := logger.Test(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + registrySel := env.AllChainSelectors()[0] + chCapReg, err := changeset.DeployCapabilityRegistry(env, registrySel) + require.NoError(t, err) + require.NotNil(t, chCapReg) + err = env.ExistingAddresses.Merge(chCapReg.AddressBook) + require.NoError(t, err) + + chOcr3, err := changeset.DeployOCR3(env, registrySel) + require.NoError(t, err) + require.NotNil(t, chOcr3) + err = env.ExistingAddresses.Merge(chOcr3.AddressBook) + require.NoError(t, err) + + chForwarder, err := changeset.DeployForwarder(env, registrySel) + require.NoError(t, err) + require.NotNil(t, chForwarder) + err = env.ExistingAddresses.Merge(chForwarder.AddressBook) + require.NoError(t, err) + + chConsumer, err := changeset.DeployFeedsConsumer(env, &changeset.DeployFeedsConsumerRequest{ + ChainSelector: registrySel, + }) + require.NoError(t, err) + require.NotNil(t, chConsumer) + err = env.ExistingAddresses.Merge(chConsumer.AddressBook) + require.NoError(t, err) + + chMcms, err := commonchangeset.DeployMCMSWithTimelock(env, map[uint64]types.MCMSWithTimelockConfig{ + registrySel: { + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + }, + }) + err = env.ExistingAddresses.Merge(chMcms.AddressBook) + require.NoError(t, err) + + require.NoError(t, err) + require.NotNil(t, chMcms) + + resp, err := changeset.TransferAllOwnership(env, &changeset.TransferAllOwnershipRequest{ + ChainSelector: registrySel, + }) + require.NoError(t, err) + require.NotNil(t, resp) +} diff --git a/deployment/keystone/consumer_deployer.go b/deployment/keystone/consumer_deployer.go new file mode 100644 index 00000000000..12d148144b5 --- /dev/null +++ b/deployment/keystone/consumer_deployer.go @@ -0,0 +1,55 @@ +package keystone + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/feeds_consumer" +) + +type KeystoneFeedsConsumerDeployer struct { + lggr logger.Logger + contract *feeds_consumer.KeystoneFeedsConsumer +} + +func NewKeystoneFeedsConsumerDeployer() (*KeystoneFeedsConsumerDeployer, error) { + lggr, err := logger.New() + if err != nil { + return nil, err + } + return &KeystoneFeedsConsumerDeployer{lggr: lggr}, nil +} + +func (c *KeystoneFeedsConsumerDeployer) deploy(req DeployRequest) (*DeployResponse, error) { + est, err := estimateDeploymentGas(req.Chain.Client, feeds_consumer.KeystoneFeedsConsumerABI) + if err != nil { + return nil, fmt.Errorf("failed to estimate gas: %w", err) + } + c.lggr.Debugf("Feeds Consumer estimated gas: %d", est) + + consumerAddr, tx, consumer, err := feeds_consumer.DeployKeystoneFeedsConsumer( + req.Chain.DeployerKey, + req.Chain.Client) + if err != nil { + return nil, fmt.Errorf("failed to deploy feeds consumer: %w", err) + } + + _, err = req.Chain.Confirm(tx) + if err != nil { + return nil, fmt.Errorf("failed to confirm and save feeds consumer: %w", err) + } + + tv := deployment.TypeAndVersion{ + Type: FeedConsumer, + } + + resp := &DeployResponse{ + Address: consumerAddr, + Tx: tx.Hash(), + Tv: tv, + } + c.contract = consumer + return resp, nil +} diff --git a/deployment/keystone/contract_set.go b/deployment/keystone/contract_set.go index ccd89f6905f..ee503a54b4d 100644 --- a/deployment/keystone/contract_set.go +++ b/deployment/keystone/contract_set.go @@ -3,8 +3,6 @@ package keystone import ( "fmt" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink/deployment" ) @@ -18,7 +16,7 @@ type deployContractSetResponse struct { deployment.AddressBook } -func deployContractsToChain(lggr logger.Logger, req deployContractsRequest) (*deployContractSetResponse, error) { +func deployContractsToChain(req deployContractsRequest) (*deployContractSetResponse, error) { if req.ad == nil { req.ad = deployment.NewMemoryAddressBook() } @@ -29,16 +27,16 @@ func deployContractsToChain(lggr logger.Logger, req deployContractsRequest) (*de // cap reg and ocr3 only deployed on registry chain if req.isRegistryChain { - err := DeployCapabilitiesRegistry(lggr, req.chain, resp.AddressBook) + _, err := DeployCapabilitiesRegistry(req.chain, resp.AddressBook) if err != nil { return nil, fmt.Errorf("failed to deploy CapabilitiesRegistry: %w", err) } - err = DeployOCR3(lggr, req.chain, resp.AddressBook) + _, err = DeployOCR3(req.chain, resp.AddressBook) if err != nil { return nil, fmt.Errorf("failed to deploy OCR3Capability: %w", err) } } - err := DeployForwarder(lggr, req.chain, resp.AddressBook) + _, err := DeployForwarder(req.chain, resp.AddressBook) if err != nil { return nil, fmt.Errorf("failed to deploy KeystoneForwarder: %w", err) } @@ -47,48 +45,71 @@ func deployContractsToChain(lggr logger.Logger, req deployContractsRequest) (*de // DeployCapabilitiesRegistry deploys the CapabilitiesRegistry contract to the chain // and saves the address in the address book. This mutates the address book. -func DeployCapabilitiesRegistry(lggr logger.Logger, chain deployment.Chain, ab deployment.AddressBook) error { - capabilitiesRegistryDeployer := CapabilitiesRegistryDeployer{lggr: lggr} +func DeployCapabilitiesRegistry(chain deployment.Chain, ab deployment.AddressBook) (*DeployResponse, error) { + capabilitiesRegistryDeployer, err := NewCapabilitiesRegistryDeployer() capabilitiesRegistryResp, err := capabilitiesRegistryDeployer.Deploy(DeployRequest{Chain: chain}) if err != nil { - return fmt.Errorf("failed to deploy CapabilitiesRegistry: %w", err) + return nil, fmt.Errorf("failed to deploy CapabilitiesRegistry: %w", err) } err = ab.Save(chain.Selector, capabilitiesRegistryResp.Address.String(), capabilitiesRegistryResp.Tv) if err != nil { - return fmt.Errorf("failed to save CapabilitiesRegistry: %w", err) + return nil, fmt.Errorf("failed to save CapabilitiesRegistry: %w", err) } - lggr.Infof("Deployed %s chain selector %d addr %s", capabilitiesRegistryResp.Tv.String(), chain.Selector, capabilitiesRegistryResp.Address.String()) - return nil + return capabilitiesRegistryResp, nil } // DeployOCR3 deploys the OCR3Capability contract to the chain // and saves the address in the address book. This mutates the address book. -func DeployOCR3(lggr logger.Logger, chain deployment.Chain, ab deployment.AddressBook) error { - ocr3Deployer := OCR3Deployer{lggr: lggr} +func DeployOCR3(chain deployment.Chain, ab deployment.AddressBook) (*DeployResponse, error) { + ocr3Deployer, err := NewOCR3Deployer() + if err != nil { + return nil, fmt.Errorf("failed to create OCR3Deployer: %w", err) + } ocr3Resp, err := ocr3Deployer.deploy(DeployRequest{Chain: chain}) if err != nil { - return fmt.Errorf("failed to deploy OCR3Capability: %w", err) + return nil, fmt.Errorf("failed to deploy OCR3Capability: %w", err) } err = ab.Save(chain.Selector, ocr3Resp.Address.String(), ocr3Resp.Tv) if err != nil { - return fmt.Errorf("failed to save OCR3Capability: %w", err) + return nil, fmt.Errorf("failed to save OCR3Capability: %w", err) } - lggr.Infof("Deployed %s chain selector %d addr %s", ocr3Resp.Tv.String(), chain.Selector, ocr3Resp.Address.String()) - return nil + + return ocr3Resp, nil } // DeployForwarder deploys the KeystoneForwarder contract to the chain // and saves the address in the address book. This mutates the address book. -func DeployForwarder(lggr logger.Logger, chain deployment.Chain, ab deployment.AddressBook) error { - forwarderDeployer := KeystoneForwarderDeployer{lggr: lggr} +func DeployForwarder(chain deployment.Chain, ab deployment.AddressBook) (*DeployResponse, error) { + forwarderDeployer, err := NewKeystoneForwarderDeployer() + if err != nil { + return nil, fmt.Errorf("failed to create KeystoneForwarderDeployer: %w", err) + } forwarderResp, err := forwarderDeployer.deploy(DeployRequest{Chain: chain}) if err != nil { - return fmt.Errorf("failed to deploy KeystoneForwarder: %w", err) + return nil, fmt.Errorf("failed to deploy KeystoneForwarder: %w", err) } err = ab.Save(chain.Selector, forwarderResp.Address.String(), forwarderResp.Tv) if err != nil { - return fmt.Errorf("failed to save KeystoneForwarder: %w", err) + return nil, fmt.Errorf("failed to save KeystoneForwarder: %w", err) + } + return forwarderResp, nil +} + +// DeployFeedsConsumer deploys the KeystoneFeedsConsumer contract to the chain +// and saves the address in the address book. This mutates the address book. +func DeployFeedsConsumer(chain deployment.Chain, ab deployment.AddressBook) (*DeployResponse, error) { + consumerDeploy, err := NewKeystoneFeedsConsumerDeployer() + if err != nil { + return nil, err + } + consumerResp, err := consumerDeploy.deploy(DeployRequest{Chain: chain}) + if err != nil { + return nil, fmt.Errorf("failed to deploy FeedsConsumer: %w", err) + } + err = ab.Save(chain.Selector, consumerResp.Address.String(), consumerResp.Tv) + if err != nil { + + return nil, fmt.Errorf("failed to save FeedsConsumer: %w", err) } - lggr.Infof("Deployed %s chain selector %d addr %s", forwarderResp.Tv.String(), chain.Selector, forwarderResp.Address.String()) - return nil + return consumerResp, nil } diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index 874b98600ae..7b304c1ba0c 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -25,6 +25,7 @@ import ( capabilitiespb "github.com/smartcontractkit/chainlink-common/pkg/capabilities/pb" "github.com/smartcontractkit/chainlink-common/pkg/values" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" kf "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" @@ -75,7 +76,7 @@ func ConfigureContracts(ctx context.Context, lggr logger.Logger, req ConfigureCo addrBook := req.Env.ExistingAddresses if req.DoContractDeploy { - contractDeployCS, err := DeployContracts(lggr, req.Env, req.RegistryChainSel) + contractDeployCS, err := DeployContracts(req.Env, req.RegistryChainSel) if err != nil { return nil, fmt.Errorf("failed to deploy contracts: %w", err) } @@ -121,12 +122,13 @@ func ConfigureContracts(ctx context.Context, lggr logger.Logger, req ConfigureCo } // DeployContracts deploys the all the keystone contracts on all chains and returns the address book in the changeset -func DeployContracts(lggr logger.Logger, e *deployment.Environment, chainSel uint64) (*deployment.ChangesetOutput, error) { +func DeployContracts(e *deployment.Environment, chainSel uint64) (*deployment.ChangesetOutput, error) { + lggr := e.Logger adbook := deployment.NewMemoryAddressBook() // deploy contracts on all chains and track the registry and ocr3 contracts for _, chain := range e.Chains { lggr.Infow("deploying contracts", "chain", chain.Selector) - deployResp, err := deployContractsToChain(lggr, deployContractsRequest{ + deployResp, err := deployContractsToChain(deployContractsRequest{ chain: chain, isRegistryChain: chain.Selector == chainSel, }, diff --git a/deployment/keystone/deploy_test.go b/deployment/keystone/deploy_test.go index a3931550cfa..b02497c22fa 100644 --- a/deployment/keystone/deploy_test.go +++ b/deployment/keystone/deploy_test.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/environment/clo" "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" @@ -113,7 +114,7 @@ func TestDeploy(t *testing.T) { } // explicitly deploy the contracts - cs, err := keystone.DeployContracts(lggr, &env, sepoliaChainSel) + cs, err := keystone.DeployContracts(&env, sepoliaChainSel) require.NoError(t, err) env.ExistingAddresses = cs.AddressBook deployReq := keystone.ConfigureContractsRequest{ @@ -311,7 +312,7 @@ func TestDeployCLO(t *testing.T) { ctx := tests.Context(t) // explicitly deploy the contracts - cs, err := keystone.DeployContracts(lggr, env, registryChainSel) + cs, err := keystone.DeployContracts(env, registryChainSel) require.NoError(t, err) // Deploy successful these are now part of our env. require.NoError(t, env.ExistingAddresses.Merge(cs.AddressBook)) diff --git a/deployment/keystone/forwarder_deployer.go b/deployment/keystone/forwarder_deployer.go index b7fde675973..cf29b20c693 100644 --- a/deployment/keystone/forwarder_deployer.go +++ b/deployment/keystone/forwarder_deployer.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" ) @@ -14,6 +15,13 @@ type KeystoneForwarderDeployer struct { contract *forwarder.KeystoneForwarder } +func NewKeystoneForwarderDeployer() (*KeystoneForwarderDeployer, error) { + lggr, err := logger.New() + if err != nil { + return nil, err + } + return &KeystoneForwarderDeployer{lggr: lggr}, nil +} func (c *KeystoneForwarderDeployer) deploy(req DeployRequest) (*DeployResponse, error) { est, err := estimateDeploymentGas(req.Chain.Client, forwarder.KeystoneForwarderABI) if err != nil { diff --git a/deployment/keystone/ocr3_deployer.go b/deployment/keystone/ocr3_deployer.go index 5f2ba34f42c..227894f7c67 100644 --- a/deployment/keystone/ocr3_deployer.go +++ b/deployment/keystone/ocr3_deployer.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/ocr3_capability" ) @@ -14,6 +15,14 @@ type OCR3Deployer struct { contract *ocr3_capability.OCR3Capability } +func NewOCR3Deployer() (*OCR3Deployer, error) { + lggr, err := logger.New() + if err != nil { + return nil, err + } + return &OCR3Deployer{lggr: lggr}, nil +} + func (c *OCR3Deployer) deploy(req DeployRequest) (*DeployResponse, error) { est, err := estimateDeploymentGas(req.Chain.Client, ocr3_capability.OCR3CapabilityABI) if err != nil { diff --git a/deployment/keystone/types.go b/deployment/keystone/types.go index b7bf636c3e2..4b00e5e4dc0 100644 --- a/deployment/keystone/types.go +++ b/deployment/keystone/types.go @@ -20,10 +20,12 @@ import ( ) var ( - CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" // https://github.com/smartcontractkit/chainlink/blob/50c1b3dbf31bd145b312739b08967600a5c67f30/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol#L392 - KeystoneForwarder deployment.ContractType = "KeystoneForwarder" // https://github.com/smartcontractkit/chainlink/blob/50c1b3dbf31bd145b312739b08967600a5c67f30/contracts/src/v0.8/keystone/KeystoneForwarder.sol#L90 - OCR3Capability deployment.ContractType = "OCR3Capability" // https://github.com/smartcontractkit/chainlink/blob/50c1b3dbf31bd145b312739b08967600a5c67f30/contracts/src/v0.8/keystone/OCR3Capability.sol#L12 - FeedConsumer deployment.ContractType = "FeedConsumer" // no type and a version in contract https://github.com/smartcontractkit/chainlink/blob/89183a8a5d22b1aeca0ade3b76d16aa84067aa57/contracts/src/v0.8/keystone/KeystoneFeedsConsumer.sol#L1 + CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" // https://github.com/smartcontractkit/chainlink/blob/50c1b3dbf31bd145b312739b08967600a5c67f30/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol#L392 + KeystoneForwarder deployment.ContractType = "KeystoneForwarder" // https://github.com/smartcontractkit/chainlink/blob/50c1b3dbf31bd145b312739b08967600a5c67f30/contracts/src/v0.8/keystone/KeystoneForwarder.sol#L90 + OCR3Capability deployment.ContractType = "OCR3Capability" // https://github.com/smartcontractkit/chainlink/blob/50c1b3dbf31bd145b312739b08967600a5c67f30/contracts/src/v0.8/keystone/OCR3Capability.sol#L12 + FeedConsumer deployment.ContractType = "FeedConsumer" // no type and a version in contract https://github.com/smartcontractkit/chainlink/blob/89183a8a5d22b1aeca0ade3b76d16aa84067aa57/contracts/src/v0.8/keystone/KeystoneFeedsConsumer.sol#L1 + RBACTimelock deployment.ContractType = "RBACTimelock" // no type and a version in contract https://github.com/smartcontractkit/ccip-owner-contracts/blob/main/src/RBACTimelock.sol + ProposerManyChainMultiSig deployment.ContractType = "ProposerManyChainMultiSig" // no type and a version in contract https://github.com/smartcontractkit/ccip-owner-contracts/blob/main/src/ManyChainMultiSig.sol ) type DeployResponse struct { diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 30ada2455ca..52276f027bc 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -4518,7 +4518,7 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] -GasLimit = 5400000 +GasLimit = 6000000 [Workflow] GasLimitDefault = 400000 @@ -4625,7 +4625,7 @@ ObservationGracePeriod = '1s' [OCR2] [OCR2.Automation] -GasLimit = 5400000 +GasLimit = 6000000 [Workflow] GasLimitDefault = 400000 diff --git a/go.mod b/go.mod index bbe94db208c..8e27aab784e 100644 --- a/go.mod +++ b/go.mod @@ -81,7 +81,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db github.com/smartcontractkit/chainlink-feeds v0.1.1 - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 + github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.2 github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749 github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 diff --git a/go.sum b/go.sum index 8532992d9e2..71832f08801 100644 --- a/go.sum +++ b/go.sum @@ -1133,8 +1133,8 @@ github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db/go.mod h1:yjb9d4q7+m8aGbjfTbkNoNuA4PeSxcUszsSZHDrvS0E= 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/orchestrator v0.3.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +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= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749/go.mod h1:nkIegLHodyrrZguxkYEHcNw2vAXv8H8xlCoLzwylcL0= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= diff --git a/integration-tests/ccip-tests/load/ccip_loadgen.go b/integration-tests/ccip-tests/load/ccip_loadgen.go index 3ce770d31bc..d562cce88b2 100644 --- a/integration-tests/ccip-tests/load/ccip_loadgen.go +++ b/integration-tests/ccip-tests/load/ccip_loadgen.go @@ -300,7 +300,7 @@ func (c *CCIPE2ELoad) Call(_ *wasp.Generator) *wasp.Response { } // the msg is no longer needed, so we can clear it to avoid holding extra data during load - // nolint:ineffassign,staticcheck + //nolint:ineffassign,staticcheck msg = router.ClientEVM2AnyMessage{} txConfirmationTime := time.Now().UTC() diff --git a/integration-tests/contracts/ccipreader_test.go b/integration-tests/contracts/ccipreader_test.go index 10363d46da5..0144ddf05f1 100644 --- a/integration-tests/contracts/ccipreader_test.go +++ b/integration-tests/contracts/ccipreader_test.go @@ -101,7 +101,7 @@ func setupGetCommitGTETimestampTest(ctx context.Context, t *testing.T, finalityD 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++ { + for i := uint8(0); int(i) < numReports; i++ { _, err := s.contract.EmitCommitReportAccepted(s.auth, ccip_reader_tester.OffRampCommitReport{ PriceUpdates: ccip_reader_tester.InternalPriceUpdates{ TokenPriceUpdates: []ccip_reader_tester.InternalTokenPriceUpdate{ @@ -122,7 +122,7 @@ func emitCommitReports(ctx context.Context, t *testing.T, s *testSetupData, numR SourceChainSelector: uint64(chainS1), MinSeqNr: 10, MaxSeqNr: 20, - MerkleRoot: [32]byte{uint8(i) + 1}, //nolint:gosec // this won't overflow + MerkleRoot: [32]byte{i + 1}, OnRampAddress: common.LeftPadBytes(onRampAddress.Bytes(), 32), }, }, diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 6a3d1179393..63a298a243f 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -420,7 +420,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.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 github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index c4252fd4aad..9e3bf3c0e90 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1442,8 +1442,8 @@ github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6An 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.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +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= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749/go.mod h1:nkIegLHodyrrZguxkYEHcNw2vAXv8H8xlCoLzwylcL0= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index eb630a1a6f3..79b5f8b2ae3 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -403,7 +403,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect - github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.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 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 2bec4cfc69d..2b1abf1a0d3 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1433,8 +1433,8 @@ github.com/smartcontractkit/chainlink-feeds v0.1.1 h1:JzvUOM/OgGQA1sOqTXXl52R6An 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.0 h1:PBUaFfPLm+Efq7H9kdfGBivH+QhJ6vB5EZTR/sCZsxI= -github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +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= github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241127201057-3c9282e39749/go.mod h1:nkIegLHodyrrZguxkYEHcNw2vAXv8H8xlCoLzwylcL0= github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 h1:B4DFdk6MGcQnoCjjMBCx7Z+GWQpxRWJ4O8W/dVJyWGA= diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go index 2b14c883238..f376fb15b4a 100644 --- a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/smartcontractkit/chainlink-common/pkg/config" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -90,8 +91,16 @@ func Test_CCIPFeeBoosting(t *testing.T) { DestSelector: destChain, InitialPricesBySource: initialPrices, FeeQuoterDestChain: changeset.DefaultFeeQuoterDestChainConfig(), + TestRouter: false, } - require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) + + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(changeset.AddLanes), + Config: changeset.AddLanesConfig{LaneConfigs: []changeset.LaneConfig{laneCfg}}, + }, + }) + require.NoError(t, err) // Update token prices in destination chain FeeQuoter err = updateTokensPrices(e, state, destChain, map[common.Address]*big.Int{ diff --git a/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go b/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go new file mode 100644 index 00000000000..221d35bd992 --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_gas_price_updates_test.go @@ -0,0 +1,126 @@ +package smoke + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + "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/gethwrappers/ccip/generated/fee_quoter" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +// Test_CCIPGasPriceUpdates tests that chain fee price updates are propagated correctly when +// price reaches some deviation threshold or when the price has expired. +func Test_CCIPGasPriceUpdates(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := changeset.Context(t) + callOpts := &bind.CallOpts{Context: ctx} + + var gasPriceExpiry = 5 * time.Second + e, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, &changeset.TestConfigs{ + OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(gasPriceExpiry) + return params + }, + }) + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + require.NoError(t, changeset.AddLanesForAll(e.Env, state)) + + allChainSelectors := maps.Keys(e.Env.Chains) + assert.GreaterOrEqual(t, len(allChainSelectors), 2, "test requires at least 2 chains") + + sourceChain1 := allChainSelectors[0] + sourceChain2 := allChainSelectors[1] + + feeQuoter1 := state.Chains[sourceChain1].FeeQuoter + feeQuoter2 := state.Chains[sourceChain2].FeeQuoter + + // get initial chain fees + initialChain2Fee, err := feeQuoter1.GetDestinationChainGasPrice(callOpts, sourceChain2) + require.NoError(t, err) + initialChain1Fee, err := feeQuoter2.GetDestinationChainGasPrice(callOpts, sourceChain1) + require.NoError(t, err) + t.Logf("initial chain1 fee (stored in chain2): %v", initialChain1Fee) + t.Logf("initial chain2 fee (stored in chain1): %v", initialChain2Fee) + + // get latest price updates sequence number from the offRamps + offRampChain1 := state.Chains[sourceChain1].OffRamp + offRampChain2 := state.Chains[sourceChain2].OffRamp + priceUpdatesSeqNumChain1, err := offRampChain1.GetLatestPriceSequenceNumber(callOpts) + require.NoError(t, err) + priceUpdatesSeqNumChain2, err := offRampChain2.GetLatestPriceSequenceNumber(callOpts) + require.NoError(t, err) + t.Logf("priceUpdatesSeqNumChain1: %v", priceUpdatesSeqNumChain1) + t.Logf("priceUpdatesSeqNumChain2: %v", priceUpdatesSeqNumChain2) + + // update the price of chain2 + tx, err := feeQuoter1.UpdatePrices(e.Env.Chains[sourceChain1].DeployerKey, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: nil, + GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ + {DestChainSelector: sourceChain2, UsdPerUnitGas: big.NewInt(5123)}, + }, + }) + require.NoError(t, err) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[sourceChain1], tx, err) + require.NoError(t, err) + + // assert that the chain fees are updated by the commit plugin reports + priceDeviationChecked := false // flag to check if price deviation condition was met + assert.Eventually(t, func() bool { + // offRamps should have updated the sequence number + priceUpdatesSeqNumChain1Now, err := offRampChain1.GetLatestPriceSequenceNumber(callOpts) + require.NoError(t, err) + priceUpdatesSeqNumChain2Now, err := offRampChain2.GetLatestPriceSequenceNumber(callOpts) + require.NoError(t, err) + t.Logf("priceUpdatesSeqNumChain1: %v", priceUpdatesSeqNumChain1Now) + t.Logf("priceUpdatesSeqNumChain2: %v", priceUpdatesSeqNumChain2Now) + if priceUpdatesSeqNumChain1Now <= priceUpdatesSeqNumChain1 { + return false + } + if priceUpdatesSeqNumChain2Now <= priceUpdatesSeqNumChain2 { + return false + } + + chain2FeeNow, err := feeQuoter1.GetDestinationChainGasPrice(callOpts, sourceChain2) + require.NoError(t, err) + chain1FeeNow, err := feeQuoter2.GetDestinationChainGasPrice(callOpts, sourceChain1) + require.NoError(t, err) + t.Logf("chainFee1 (stored in chain2): %v", chain1FeeNow) + t.Logf("chainFee2 (stored in chain1): %v", chain2FeeNow) + + if !priceDeviationChecked { + // make sure there was a price update for chain2 when price deviation was reached + if chain2FeeNow.Value.Cmp(initialChain2Fee.Value) == 0 { + t.Logf("chainFee2 not updated: %v original=%v", chain2FeeNow, initialChain2Fee.Value) + return false + } + require.NotEqual(t, chain2FeeNow.Timestamp, initialChain2Fee.Timestamp) + priceDeviationChecked = true + } + + // make sure there was a price update for chain1 but with the same price - when expiration is reached + if chain1FeeNow.Timestamp == initialChain1Fee.Timestamp { + t.Logf("chainFee1 timestamp not updated: %v original=%v", chain1FeeNow, initialChain1Fee.Timestamp) + initialChain1Fee = chain1FeeNow + return false + } + if chain1FeeNow.Value.Cmp(initialChain1Fee.Value) != 0 { + t.Logf("chainFee1 changed: %v prev:%v", chain1FeeNow, initialChain1Fee.Value) + initialChain1Fee = chain1FeeNow + return false + } + + return priceDeviationChecked + }, tests.WaitTimeout(t), 500*time.Millisecond) +} diff --git a/integration-tests/smoke/ccip/ccip_message_limitations_test.go b/integration-tests/smoke/ccip/ccip_message_limitations_test.go new file mode 100644 index 00000000000..a86644bae7e --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_message_limitations_test.go @@ -0,0 +1,183 @@ +package smoke + +import ( + "math/big" + "slices" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + + "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" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func Test_CCIPMessageLimitations(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := testcontext.Get(t) + callOpts := &bind.CallOpts{Context: ctx} + + testEnv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, &changeset.TestConfigs{}) + chains := maps.Keys(testEnv.Env.Chains) + + onChainState, err := changeset.LoadOnchainState(testEnv.Env) + require.NoError(t, err) + + require.NoError(t, changeset.AddLanesForAll(testEnv.Env, onChainState)) + + srcToken, _ := setupTokens( + t, + onChainState, + testEnv, + chains[0], + chains[1], + deployment.E18Mult(10_000), + deployment.E18Mult(10_000), + ) + + chain0DestConfig, err := onChainState.Chains[chains[0]].FeeQuoter.GetDestChainConfig(callOpts, chains[1]) + require.NoError(t, err) + t.Logf("0->1 destination config: %+v", chain0DestConfig) + + testMsgs := []struct { + name string + fromChain uint64 + toChain uint64 + msg router.ClientEVM2AnyMessage + expRevert bool + }{ + { + name: "hit limit on data", + fromChain: chains[0], + toChain: chains[1], + msg: router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[chains[1]].Receiver.Address().Bytes(), 32), + Data: []byte(strings.Repeat("0", int(chain0DestConfig.MaxDataBytes))), + FeeToken: common.HexToAddress("0x0"), + }, + }, + { + name: "hit limit on tokens", + fromChain: chains[0], + toChain: chains[1], + msg: router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[chains[1]].Receiver.Address().Bytes(), 32), + TokenAmounts: slices.Repeat([]router.ClientEVMTokenAmount{ + {Token: srcToken.Address(), Amount: big.NewInt(1)}, + }, int(chain0DestConfig.MaxNumberOfTokensPerMsg)), + FeeToken: common.HexToAddress("0x0"), + }, + }, + { + name: "hit limit on gas limit", + fromChain: chains[0], + toChain: chains[1], + msg: router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[chains[1]].Receiver.Address().Bytes(), 32), + Data: []byte(strings.Repeat("0", int(chain0DestConfig.MaxDataBytes))), + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: changeset.MakeEVMExtraArgsV2(uint64(chain0DestConfig.MaxPerMsgGasLimit), true), + }, + }, + //{ // TODO: exec plugin never executed this message. CCIP-4471 + // name: "hit limit on maxDataBytes, tokens, gasLimit should succeed", + // fromChain: chains[0], + // toChain: chains[1], + // msg: router.ClientEVM2AnyMessage{ + // Receiver: common.LeftPadBytes(onChainState.Chains[chains[1]].Receiver.Address().Bytes(), 32), + // Data: []byte(strings.Repeat("0", int(chain0DestConfig.MaxDataBytes))), + // TokenAmounts: slices.Repeat([]router.ClientEVMTokenAmount{ + // {Token: srcToken.Address(), Amount: big.NewInt(1)}, + // }, int(chain0DestConfig.MaxNumberOfTokensPerMsg)), + // FeeToken: common.HexToAddress("0x0"), + // ExtraArgs: changeset.MakeEVMExtraArgsV2(uint64(chain0DestConfig.MaxPerMsgGasLimit), true), + // }, + //}, + { + name: "exceeding maxDataBytes", + fromChain: chains[0], + toChain: chains[1], + msg: router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[chains[1]].Receiver.Address().Bytes(), 32), + Data: []byte(strings.Repeat("0", int(chain0DestConfig.MaxDataBytes)+1)), + TokenAmounts: []router.ClientEVMTokenAmount{}, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }, + expRevert: true, + }, + { + name: "exceeding number of tokens", + fromChain: chains[0], + toChain: chains[1], + msg: router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[chains[1]].Receiver.Address().Bytes(), 32), + Data: []byte("abc"), + TokenAmounts: slices.Repeat([]router.ClientEVMTokenAmount{ + {Token: srcToken.Address(), Amount: big.NewInt(1)}, + }, int(chain0DestConfig.MaxNumberOfTokensPerMsg)+1), + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }, + expRevert: true, + }, + { + name: "exceeding gas limit", + fromChain: chains[0], + toChain: chains[1], + msg: router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[chains[1]].Receiver.Address().Bytes(), 32), + Data: []byte("abc"), + TokenAmounts: []router.ClientEVMTokenAmount{}, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: changeset.MakeEVMExtraArgsV2(uint64(chain0DestConfig.MaxPerMsgGasLimit)+1, true), + }, + expRevert: true, + }, + } + + // 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) + for _, msg := range testMsgs { + t.Logf("Sending msg: %s", msg.name) + require.NotEqual(t, msg.fromChain, msg.toChain, "fromChain and toChain cannot be the same") + startBlocks[msg.toChain] = nil + msgSentEvent, err := changeset.DoSendRequest( + t, testEnv.Env, onChainState, msg.fromChain, msg.toChain, false, msg.msg) + + if msg.expRevert { + t.Logf("Message reverted as expected") + require.Error(t, err) + require.Contains(t, err.Error(), "execution reverted") + continue + } + require.NoError(t, err) + + t.Logf("Message not reverted as expected") + + expectedSeqNum[changeset.SourceDestPair{ + SourceChainSelector: msg.fromChain, + DestChainSelector: msg.toChain, + }] = msgSentEvent.SequenceNumber + + expectedSeqNumExec[changeset.SourceDestPair{ + SourceChainSelector: msg.fromChain, + DestChainSelector: msg.toChain, + }] = []uint64{msgSentEvent.SequenceNumber} + } + + // Wait for all commit reports to land. + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, testEnv.Env, onChainState, expectedSeqNum, startBlocks) + // Wait for all exec reports to land + changeset.ConfirmExecWithSeqNrsForAll(t, testEnv.Env, onChainState, expectedSeqNumExec, startBlocks) +} diff --git a/integration-tests/smoke/ccip/ccip_token_price_updates_test.go b/integration-tests/smoke/ccip/ccip_token_price_updates_test.go new file mode 100644 index 00000000000..6a193397d7e --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_token_price_updates_test.go @@ -0,0 +1,152 @@ +package smoke + +import ( + "context" + "math" + "math/big" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" + "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/fee_quoter" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func Test_CCIPTokenPriceUpdates(t *testing.T) { + lggr := logger.TestLogger(t) + ctx := changeset.Context(t) + callOpts := &bind.CallOpts{Context: ctx} + + var tokenPriceExpiry = 5 * time.Second + e, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, &changeset.TestConfigs{ + OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(tokenPriceExpiry) + return params + }, + }) + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + require.NoError(t, changeset.AddLanesForAll(e.Env, state)) + + allChainSelectors := maps.Keys(e.Env.Chains) + assert.GreaterOrEqual(t, len(allChainSelectors), 2, "test requires at least 2 chains") + + sourceChain1 := allChainSelectors[0] + + feeQuoter1 := state.Chains[sourceChain1].FeeQuoter + + feeTokensChain1, err := feeQuoter1.GetFeeTokens(callOpts) + require.NoError(t, err) + t.Logf("feeTokens: %v", feeTokensChain1) + + tokenPricesBefore, err := feeQuoter1.GetTokenPrices(callOpts, feeTokensChain1) + require.NoError(t, err) + t.Logf("tokenPrices: %v", tokenPricesBefore) + + // assert token prices updated due to time expiration + assert.Eventually(t, func() bool { + tokenPricesNow, err := feeQuoter1.GetTokenPrices(callOpts, feeTokensChain1) + require.NoError(t, err) + t.Logf("tokenPrices: %v", tokenPricesNow) + + // both tokens should have same price but different timestamp since there was an update due to time deviation + for i, price := range tokenPricesNow { + if tokenPricesBefore[i].Timestamp == price.Timestamp { + tokenPricesBefore = tokenPricesNow + return false // timestamp is the same + } + if tokenPricesBefore[i].Value.Cmp(price.Value) != 0 { + tokenPricesBefore = tokenPricesNow + return false // price was updated + } + } + t.Log("time expiration assertions complete") + return true + }, tests.WaitTimeout(t), 500*time.Millisecond) + + // disable oracles to prevent price updates while we manually edit token prices + disabledOracleIDs := disableOracles(ctx, t, e.Env.Offchain) + + assert.Eventually(t, func() bool { + // manually update token prices by setting values to maxUint64 and 0 + tx, err := feeQuoter1.UpdatePrices(e.Env.Chains[sourceChain1].DeployerKey, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: []fee_quoter.InternalTokenPriceUpdate{ + {SourceToken: feeTokensChain1[0], UsdPerToken: big.NewInt(0).SetUint64(math.MaxUint64)}, + {SourceToken: feeTokensChain1[1], UsdPerToken: big.NewInt(0)}, + }, + }) + require.NoError(t, err) + + _, err = deployment.ConfirmIfNoError(e.Env.Chains[sourceChain1], tx, err) + require.NoError(t, err) + t.Logf("manually editing token prices") + + tokenPricesNow, err := feeQuoter1.GetTokenPrices(callOpts, feeTokensChain1) + require.NoError(t, err) + t.Logf("tokenPrices straight after: %v", tokenPricesNow) + + if uint64(math.MaxUint64) != tokenPricesNow[0].Value.Uint64() { + return false + } + if uint64(0) != tokenPricesNow[1].Value.Uint64() { + return false + } + return true + + // retry because there might've been a commit report inflight + }, tests.WaitTimeout(t), 200*time.Millisecond) + + enableOracles(ctx, t, e.Env.Offchain, disabledOracleIDs) + + // wait until price goes back to the original + assert.Eventually(t, func() bool { + tokenPricesNow, err := feeQuoter1.GetTokenPrices(callOpts, feeTokensChain1) + require.NoError(t, err) + t.Logf("tokenPrices: %v tokenPricesBefore: %v", tokenPricesNow, tokenPricesBefore) + + if tokenPricesNow[0].Value.Cmp(tokenPricesBefore[0].Value) != 0 { + return false + } + if tokenPricesNow[1].Value.Cmp(tokenPricesBefore[1].Value) != 0 { + return false + } + return true + }, tests.WaitTimeout(t), 500*time.Millisecond) +} + +func disableOracles(ctx context.Context, t *testing.T, client deployment.OffchainClient) []string { + var disabledOracleIDs []string + listNodesResp, err := client.ListNodes(ctx, &node.ListNodesRequest{}) + require.NoError(t, err) + + for _, n := range listNodesResp.Nodes { + if strings.HasPrefix(n.Name, "bootstrap") { + continue + } + _, err := client.DisableNode(ctx, &node.DisableNodeRequest{Id: n.Id}) + require.NoError(t, err) + disabledOracleIDs = append(disabledOracleIDs, n.Id) + t.Logf("node %s disabled", n.Id) + } + + return disabledOracleIDs +} + +func enableOracles(ctx context.Context, t *testing.T, client deployment.OffchainClient, oracleIDs []string) { + for _, n := range oracleIDs { + _, err := client.EnableNode(ctx, &node.EnableNodeRequest{Id: n}) + require.NoError(t, err) + t.Logf("node %s enabled", n) + } +} diff --git a/integration-tests/smoke/ccip/ccip_token_transfer_test.go b/integration-tests/smoke/ccip/ccip_token_transfer_test.go index 870648508e0..c1405314ab4 100644 --- a/integration-tests/smoke/ccip/ccip_token_transfer_test.go +++ b/integration-tests/smoke/ccip/ccip_token_transfer_test.go @@ -103,6 +103,7 @@ func TestTokenTransfer(t *testing.T) { tokenAmounts []router.ClientEVMTokenAmount receiver common.Address data []byte + extraData []byte expectedTokenBalances map[common.Address]*big.Int expectedExecutionState int }{ @@ -156,13 +157,59 @@ func TestTokenTransfer(t *testing.T) { Amount: oneE18, }, }, - receiver: state.Chains[sourceChain].Receiver.Address(), + receiver: state.Chains[sourceChain].Receiver.Address(), + extraData: changeset.MakeEVMExtraArgsV2(300_000, false), expectedTokenBalances: map[common.Address]*big.Int{ selfServeSrcToken.Address(): new(big.Int).Add(oneE18, oneE18), srcToken.Address(): oneE18, }, expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS, }, + { + name: "Sending token transfer with custom gasLimits to the EOA is successful", + srcChain: destChain, + dstChain: sourceChain, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: selfServeDestToken.Address(), + Amount: oneE18, + }, + { + Token: destToken.Address(), + Amount: new(big.Int).Add(oneE18, oneE18), + }, + }, + receiver: utils.RandomAddress(), + extraData: changeset.MakeEVMExtraArgsV2(1, false), + expectedTokenBalances: map[common.Address]*big.Int{ + selfServeSrcToken.Address(): oneE18, + srcToken.Address(): new(big.Int).Add(oneE18, oneE18), + }, + expectedExecutionState: changeset.EXECUTION_STATE_SUCCESS, + }, + { + name: "Sending PTT with too low gas limit leads to the revert when receiver is a contract", + srcChain: destChain, + dstChain: sourceChain, + tokenAmounts: []router.ClientEVMTokenAmount{ + { + Token: selfServeDestToken.Address(), + Amount: oneE18, + }, + { + Token: destToken.Address(), + Amount: oneE18, + }, + }, + receiver: state.Chains[sourceChain].Receiver.Address(), + data: []byte("this should be reverted because gasLimit is too low, no tokens are transferred as well"), + extraData: changeset.MakeEVMExtraArgsV2(1, false), + expectedTokenBalances: map[common.Address]*big.Int{ + selfServeSrcToken.Address(): big.NewInt(0), + srcToken.Address(): big.NewInt(0), + }, + expectedExecutionState: changeset.EXECUTION_STATE_FAILURE, + }, } for _, tt := range tcs { @@ -184,6 +231,7 @@ func TestTokenTransfer(t *testing.T) { tt.receiver, tt.data, tt.expectedExecutionState, + tt.extraData, ) for token, balance := range tt.expectedTokenBalances { diff --git a/integration-tests/smoke/ccip/ccip_usdc_test.go b/integration-tests/smoke/ccip/ccip_usdc_test.go index f478392c0f7..97673d59aac 100644 --- a/integration-tests/smoke/ccip/ccip_usdc_test.go +++ b/integration-tests/smoke/ccip/ccip_usdc_test.go @@ -201,6 +201,7 @@ func TestUSDCTokenTransfer(t *testing.T) { tt.receiver, tt.data, tt.expectedExecutionState, + nil, ) for token, balance := range tt.expectedTokenBalances { diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index a2c680ee814..8ffce77cc6b 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -140,16 +140,15 @@ func NewLocalDevEnvironment( if tCfg.IsUSDC { usdcChains = allChains } - mcmsCfgPerChain := commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, c := range env.AllChainSelectors() { - mcmsCfg[c] = mcmsCfgPerChain + mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } } // Need to deploy prerequisites first so that we can form the USDC config // no proposals to be made, timelock can be passed as nil here @@ -189,64 +188,56 @@ func NewLocalDevEnvironment( }, }) require.NoError(t, err) - state, err := changeset.LoadOnchainState(env) require.NoError(t, err) - tokenConfig := changeset.NewTestTokenConfig(state.Chains[feedSel].USDFeeds) - usdcCCTPConfig := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) - timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) - ocrParams := make(map[uint64]changeset.CCIPOCRParams) - for _, chain := range usdcChains { - require.NotNil(t, state.Chains[chain].MockUSDCTokenMessenger) - require.NotNil(t, state.Chains[chain].MockUSDCTransmitter) - require.NotNil(t, state.Chains[chain].USDCTokenPool) - usdcCCTPConfig[cciptypes.ChainSelector(chain)] = pluginconfig.USDCCCTPTokenConfig{ - SourcePoolAddress: state.Chains[chain].USDCTokenPool.Address().String(), - SourceMessageTransmitterAddr: state.Chains[chain].MockUSDCTransmitter.Address().String(), - } - } - var usdcAttestationCfg changeset.USDCAttestationConfig + + var tokenDataProviders []pluginconfig.TokenDataObserverConfig if len(usdcChains) > 0 { var endpoint string err = ccipactions.SetMockServerWithUSDCAttestation(testEnv.MockAdapter, nil) require.NoError(t, err) endpoint = testEnv.MockAdapter.InternalEndpoint - usdcAttestationCfg = changeset.USDCAttestationConfig{ - API: endpoint, - APITimeout: commonconfig.MustNewDuration(time.Second), - APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + cctpContracts := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) + for _, usdcChain := range usdcChains { + cctpContracts[cciptypes.ChainSelector(usdcChain)] = pluginconfig.USDCCCTPTokenConfig{ + SourcePoolAddress: state.Chains[usdcChain].USDCTokenPool.Address().String(), + SourceMessageTransmitterAddr: state.Chains[usdcChain].MockUSDCTransmitter.Address().String(), + } } - } - require.NotNil(t, state.Chains[feedSel].LinkToken) - require.NotNil(t, state.Chains[feedSel].Weth9) - + tokenDataProviders = append(tokenDataProviders, pluginconfig.TokenDataObserverConfig{ + Type: pluginconfig.USDCCCTPHandlerType, + Version: "1.0", + USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ + Tokens: cctpContracts, + AttestationAPI: endpoint, + AttestationAPITimeout: commonconfig.MustNewDuration(time.Second), + AttestationAPIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + }}) + } + + // Build the per chain config. + tokenConfig := changeset.NewTestTokenConfig(state.Chains[feedSel].USDFeeds) + chainConfigs := make(map[uint64]changeset.CCIPOCRParams) + timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock - tokenInfo := tokenConfig.GetTokenInfo(env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - - params := changeset.DefaultOCRParams(feedSel, tokenInfo) + tokenInfo := tokenConfig.GetTokenInfo(e.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) + ocrParams := changeset.DefaultOCRParams(feedSel, tokenInfo, tokenDataProviders) if tCfg.OCRConfigOverride != nil { - params = tCfg.OCRConfigOverride(params) + ocrParams = tCfg.OCRConfigOverride(ocrParams) } - - ocrParams[chain] = params + chainConfigs[chain] = ocrParams } + // Deploy second set of changesets to deploy and configure the CCIP contracts. env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureNewChains), Config: changeset.NewChainsConfig{ - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ChainsToDeploy: allChains, - TokenConfig: tokenConfig, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - OCRParams: ocrParams, - USDCConfig: changeset.USDCConfig{ - EnabledChains: usdcChains, - USDCAttestationConfig: usdcAttestationCfg, - CCTPTokenConfig: usdcCCTPConfig, - }, + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + ChainConfigByChain: chainConfigs, }, }, { @@ -656,7 +647,7 @@ func FundNodes(t *testing.T, lggr zerolog.Logger, env *test_env.CLClusterTestEnv require.NoError(t, fundGrp.Wait(), "Error funding chainlink nodes") } -// CreateChainConfigFromNetworks creates a list of ChainConfig from the network config provided in test config. +// CreateChainConfigFromNetworks creates a list of CCIPOCRParams from the network config provided in test config. // It either creates it from the private ethereum networks created by the test environment or from the // network URLs provided in the network config ( if the network is a live testnet). // It uses the private keys from the network config to create the deployer key for each chain.