diff --git a/.changeset/changelog-generator.js b/.changeset/changelog-generator.js new file mode 100644 index 00000000000..3bc0009ef5a --- /dev/null +++ b/.changeset/changelog-generator.js @@ -0,0 +1,105 @@ +/* + * Based off of https://github.com/changesets/changesets/blob/7323704dff6e76f488370db384579b86c95c866f/packages/changelog-github/src/index.ts + */ + +const ghInfo = require("@changesets/get-github-info"); + +const getDependencyReleaseLine = async (changesets, dependenciesUpdated, options) => { + if (dependenciesUpdated.length === 0) return ""; + if (!options || !options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + + const changesetLink = `- Updated dependencies [${( + await Promise.all( + changesets.map(async (cs) => { + if (cs.commit) { + let { links } = await ghInfo.getInfo({ + repo: options.repo, + commit: cs.commit, + }); + return links.commit; + } + }) + ) + ) + .filter((_) => _) + .join(", ")}]:`; + + + const updatedDepsList = dependenciesUpdated.map( + (dependency) => ` - ${dependency.name}@${dependency.newVersion}` + ); + + return [changesetLink, ...updatedDepsList].join("\n"); +}; + +const getReleaseLine = async (changeset, _, options) => { + if (!options || !options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + + let prFromSummary; + let commitFromSummary; + + const replacedChangelog = changeset.summary + .replace(/^\s*(?:pr|pull|pull\s+request):\s*#?(\d+)/im, (_, pr) => { + let num = Number(pr); + if (!isNaN(num)) prFromSummary = num; + return ""; + }) + .replace(/^\s*commit:\s*([^\s]+)/im, (_, commit) => { + commitFromSummary = commit; + return ""; + }) + .trim(); + + const [firstLine, ...futureLines] = replacedChangelog + .split("\n") + .map((l) => l.trimRight()); + + const links = await (async () => { + if (prFromSummary !== undefined) { + let { links } = await ghInfo.getInfoFromPullRequest({ + repo: options.repo, + pull: prFromSummary, + }); + if (commitFromSummary) { + const shortCommitId = commitFromSummary.slice(0, 7); + links = { + ...links, + commit: `[\`${shortCommitId}\`](https://github.com/${options.repo}/commit/${commitFromSummary})`, + }; + } + return links; + } + const commitToFetchFrom = commitFromSummary || changeset.commit; + if (commitToFetchFrom) { + let { links } = await ghInfo.getInfo({ + repo: options.repo, + commit: commitToFetchFrom, + }); + return links; + } + return { + commit: null, + pull: null, + user: null, + }; + })(); + + const prefix = [ + links.pull === null ? "" : ` ${links.pull}`, + links.commit === null ? "" : ` ${links.commit}`, + ].join(""); + + return `\n\n-${prefix ? `${prefix} -` : ""} ${firstLine}\n${futureLines + .map((l) => ` ${l}`) + .join("\n")}`; +}; + +module.exports = { getReleaseLine, getDependencyReleaseLine }; diff --git a/.changeset/config.json b/.changeset/config.json index 4bdbe5141fb..e394800a89a 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,7 +1,7 @@ { "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", "changelog": [ - "@changesets/changelog-github", + "./changelog-generator.js", { "repo": "smartcontractkit/chainlink" } diff --git a/.changeset/green-crabs-joke.md b/.changeset/green-crabs-joke.md deleted file mode 100644 index 4e9480e9c89..00000000000 --- a/.changeset/green-crabs-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"chainlink": patch ---- - -#bugfix Update DA oracle config struct members to pointers diff --git a/.changeset/rotten-timers-give.md b/.changeset/rotten-timers-give.md new file mode 100644 index 00000000000..ef474a17d56 --- /dev/null +++ b/.changeset/rotten-timers-give.md @@ -0,0 +1,6 @@ +--- +"chainlink": minor +--- + +Support multiple chains evm clients for TXM gas estimator to fetch L1 gas oracle +#added diff --git a/.changeset/short-gifts-eat.md b/.changeset/short-gifts-eat.md new file mode 100644 index 00000000000..afc171715d4 --- /dev/null +++ b/.changeset/short-gifts-eat.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +use last mined nonce instead of pending nonce to recover from occasional nonce gap issues within nonce tracker. #internal diff --git a/.changeset/tasty-years-behave.md b/.changeset/tasty-years-behave.md new file mode 100644 index 00000000000..08019e3dafa --- /dev/null +++ b/.changeset/tasty-years-behave.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Modify release changelog generation format #internal diff --git a/.changeset/tricky-candles-matter.md b/.changeset/tricky-candles-matter.md new file mode 100644 index 00000000000..0dc7806703a --- /dev/null +++ b/.changeset/tricky-candles-matter.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#bugfix Memory leak fix on promwrapper diff --git a/.github/actions/setup-go/action.yml b/.github/actions/setup-go/action.yml index 72310783eef..bc52b5498ab 100644 --- a/.github/actions/setup-go/action.yml +++ b/.github/actions/setup-go/action.yml @@ -21,6 +21,10 @@ inputs: Only restore the module cache, don't automatically update it. Leave the updating to go-mod-cache.yml. default: "true" + restore-build-cache-only: + description: | + Only restore the build cache, don't automatically update/upload it. + default: "false" runs: using: composite @@ -81,7 +85,7 @@ runs: - uses: actions/cache/restore@v4.1.1 name: Cache Go Build Outputs (restore) # For certain events, we don't necessarily want to create a build cache, but we will benefit from restoring from one. - if: ${{ inputs.only-modules == 'false' && github.event == 'merge_group' }} + if: ${{ inputs.only-modules == 'false' && (github.event == 'merge_group' || inputs.restore-build-cache-only == 'true') }} with: path: | ${{ steps.go-cache-dir.outputs.gobuildcache }} @@ -93,8 +97,8 @@ runs: ${{ runner.os }}-gobuild-${{ inputs.cache-version }}- - uses: actions/cache@v4.1.1 - # don't save cache on merge queue events, only restore - if: ${{ inputs.only-modules == 'false' && github.event != 'merge_group' }} + # don't save cache on merge queue events + if: ${{ inputs.only-modules == 'false' && (github.event != 'merge_group' && inputs.restore-build-cache-only == 'false') }} name: Cache Go Build Outputs with: path: | diff --git a/.github/workflows/build-publish-develop-pr.yml b/.github/workflows/build-publish-develop-pr.yml index 467411ab4ea..caf46c1a3ed 100644 --- a/.github/workflows/build-publish-develop-pr.yml +++ b/.github/workflows/build-publish-develop-pr.yml @@ -56,7 +56,7 @@ jobs: - uses: actions/cache/restore@v4 with: - path: dist/linux_arm64 + path: dist/linux_arm64_v8.0 key: chainlink-arm64-${{ github.sha }} fail-on-cache-miss: true @@ -86,7 +86,7 @@ jobs: - runner: ubuntu-24.04-4cores-16GB-ARM goarch: arm64 - dist_name: linux_arm64 + dist_name: linux_arm64_v8.0 steps: - name: Checkout repository uses: actions/checkout@v4.2.1 diff --git a/.github/workflows/build-publish-goreleaser.yml b/.github/workflows/build-publish-goreleaser.yml index ca28d767398..8e61b84c4ad 100644 --- a/.github/workflows/build-publish-goreleaser.yml +++ b/.github/workflows/build-publish-goreleaser.yml @@ -59,7 +59,7 @@ jobs: - uses: actions/cache/restore@v4 with: - path: dist/linux_arm64 + path: dist/linux_arm64_v8.0 key: chainlink-arm64-${{ github.sha }}-${{ github.ref_name }} fail-on-cache-miss: true @@ -87,7 +87,7 @@ jobs: - runner: ubuntu-24.04-4cores-16GB-ARM goarch: arm64 - dist_name: linux_arm64 + dist_name: linux_arm64_v8.0 environment: build-publish permissions: id-token: write diff --git a/.github/workflows/changeset.yml b/.github/workflows/changeset.yml index 7c60a2d13de..19d17ddb588 100644 --- a/.github/workflows/changeset.yml +++ b/.github/workflows/changeset.yml @@ -30,6 +30,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4.2.1 + with: + fetch-depth: 0 - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: files-changed @@ -52,7 +54,7 @@ jobs: - '!core/chainlink.Dockerfile' - '!core/gethwrappers/**' core-changeset: - - added: '.changeset/**' + - added: '.changeset/*.md' - name: Check for changeset tags for core id: changeset-tags diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 862fefce320..3d7050197a6 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -28,13 +28,14 @@ on: type: string jobs: - filter: # No need to run core tests if there are only changes to the integration-tests + filter: name: Detect Changes permissions: pull-requests: read outputs: - changes: ${{ steps.ignore-filter.outputs.changes || steps.changes.outputs.changes }} - deployment-changes: ${{ steps.deployment-changes.outputs.changes }} + deployment-changes: ${{ steps.match-some.outputs.deployment == 'true' }} + should-run-ci-core: ${{ steps.match-every.outputs.non-ignored == 'true' || steps.match-some.outputs.core-ci == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' }} + should-run-golangci: ${{ steps.match-every.outputs.non-integration-tests == 'true' || github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest steps: - name: Checkout the repo @@ -42,23 +43,49 @@ jobs: with: repository: smartcontractkit/chainlink - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - id: changes + id: match-some with: + # "if any changed file matches one or more of the conditions" (https://github.com/dorny/paths-filter/issues/225) + predicate-quantifier: some + # deployment - any changes to files in `deployments/` filters: | - changes: + deployment: - 'deployment/**' - - '!integration-tests/**' + core-ci: + - '.github/workflows/ci-core.yml' + - '.github/actions/**' - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 - id: deployment-changes + id: match-every with: + # "if any changed file match all of the conditions" (https://github.com/dorny/paths-filter/issues/225) + predicate-quantifier: every + # non-integration-tests - only changes made outside of the `integration-tests` directory + # everything-except-ignored - only changes except for the negated ones + # - This is opt-in on purpose. To be safe, new files are assumed to have an affect on CI Core unless listed here specifically. filters: | - changes: - - 'deployment/**' - - name: Ignore Filter On Workflow Dispatch - if: ${{ github.event_name == 'workflow_dispatch' }} - id: ignore-filter - run: echo "changes=true" >> $GITHUB_OUTPUT - + non-integration-tests: + - '**' + - '!integration-tests/**' + non-ignored: + - '**' + - '!docs/**' + - '!integration-tests/**' + - '!tools/secrets/**' + - '!tools/goreleaser-config/**' + - '!tools/docker/**' + - '!tools/benchmark/**' + - '!**/README.md' + - '!**/CHANGELOG.md' + - '!.goreleaser.develop.yaml' + - '!.goreleaser.devspace.yaml' + - '!.goreleaser.production.yaml' + - '!*.nix' + - '!sonar-project.properties' + - '!nix.conf' + - '!nix-darwin-shell-hook.sh' + - '!LICENSE' + - '!.github/**' + golangci: # We don't directly merge dependabot PRs, so let's not waste the resources if: ${{ (github.event_name == 'pull_request' || github.event_name == 'schedule') && github.actor != 'dependabot[bot]' }} @@ -75,7 +102,7 @@ jobs: - uses: actions/checkout@v4.2.1 - name: Golang Lint uses: ./.github/actions/golangci-lint - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-golangci == 'true' }} with: id: core name: lint @@ -129,56 +156,56 @@ jobs: find . -type f,d -exec touch -r {} -d '1970-01-01T00:00:01' {} \; || true - name: Setup NodeJS - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} uses: ./.github/actions/setup-nodejs with: prod: "true" - name: Setup Go - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} uses: ./.github/actions/setup-go with: - # race/fuzz tests don't benefit from build caching - only-modules: ${{ matrix.type.cmd == 'go_core_race_tests' || matrix.type.cmd == 'go_core_fuzz' }} + # race/fuzz tests don't benefit repeated caching, so restore from develop's build cache + restore-build-cache-only: ${{ matrix.type.cmd == 'go_core_race_tests' || matrix.type.cmd == 'go_core_fuzz' }} build-cache-version: ${{ matrix.type.cmd }} - name: Replace chainlink-evm deps - if: ${{ needs.filter.outputs.changes == 'true' && inputs.evm-ref != ''}} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' && inputs.evm-ref != ''}} shell: bash run: go get github.com/smartcontractkit/chainlink-integrations/evm/relayer@${{ inputs.evm-ref }} - name: Setup Solana - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} uses: ./.github/actions/setup-solana - name: Setup wasmd - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} uses: ./.github/actions/setup-wasmd - name: Setup Postgres - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} uses: ./.github/actions/setup-postgres - name: Touching core/web/assets/index.html - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} run: mkdir -p core/web/assets && touch core/web/assets/index.html - name: Download Go vendor packages - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} run: go mod download - name: Build binary - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} run: go build -o chainlink.test . - name: Setup DB - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} run: ./chainlink.test local db preparetest env: CL_DATABASE_URL: ${{ env.DB_URL }} - name: Install LOOP Plugins - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} run: | pushd $(go list -m -f "{{.Dir}}" github.com/smartcontractkit/chainlink-feeds) go install ./cmd/chainlink-feeds @@ -195,34 +222,34 @@ jobs: - name: Increase Race Timeout # Increase race timeout for scheduled runs only - if: ${{ github.event.schedule != '' && needs.filter.outputs.changes == 'true' }} + if: ${{ github.event.schedule != '' && needs.filter.outputs.should-run-ci-core == 'true' }} run: | echo "TIMEOUT=10m" >> $GITHUB_ENV echo "COUNT=50" >> $GITHUB_ENV - name: Install gotestloghelper + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/gotestloghelper@v1.50.0 - name: Run tests - if: ${{ needs.filter.outputs.changes == 'true' }} + if: ${{ needs.filter.outputs.should-run-ci-core == 'true' }} id: run-tests env: OUTPUT_FILE: ./output.txt USE_TEE: false CL_DATABASE_URL: ${{ env.DB_URL }} - run: ./tools/bin/${{ matrix.type.cmd }} ./... - name: Print Filtered Test Results - if: ${{ failure() && needs.filter.outputs.changes == 'true' && steps.run-tests.conclusion == 'failure' }} + if: ${{ failure() && needs.filter.outputs.should-run-ci-core == 'true' && steps.run-tests.conclusion == 'failure' }} run: | if [[ "${{ matrix.type.printResults }}" == "true" ]]; then cat output.txt | gotestloghelper -ci fi - + - name: Print Races id: print-races - if: ${{ failure() && matrix.type.cmd == 'go_core_race_tests' && needs.filter.outputs.changes == 'true' }} + if: ${{ failure() && matrix.type.cmd == 'go_core_race_tests' && needs.filter.outputs.should-run-ci-core == 'true' }} run: | find race.* | xargs cat > race.txt if [[ -s race.txt ]]; then @@ -235,12 +262,12 @@ jobs: echo "github.ref: ${{ github.ref }}" - name: Print postgres logs - if: ${{ always() && needs.filter.outputs.changes == 'true' }} + if: ${{ always() && needs.filter.outputs.should-run-ci-core == 'true' }} run: docker compose logs postgres | tee ../../../postgres_logs.txt working-directory: ./.github/actions/setup-postgres - name: Store logs artifacts - if: ${{ always() && needs.filter.outputs.changes == 'true' }} + if: ${{ always() && needs.filter.outputs.should-run-ci-core == 'true' }} uses: actions/upload-artifact@v4.4.3 with: name: ${{ matrix.type.cmd }}_logs @@ -252,8 +279,13 @@ jobs: ./postgres_logs.txt retention-days: 7 - - name: Notify Slack - if: ${{ failure() && steps.print-races.outputs.post_to_slack == 'true' && matrix.type.cmd == 'go_core_race_tests' && (github.event_name == 'merge_group' || github.ref == 'refs/heads/develop') && needs.filter.outputs.changes == 'true' }} + - name: Notify Slack on Race Test Failure + if: | + failure() && + matrix.type.cmd == 'go_core_race_tests' && + steps.print-races.outputs.post_to_slack == 'true' && + (github.event_name == 'merge_group' || github.ref == 'refs/heads/develop') && + needs.filter.outputs.should-run-ci-core == 'true' uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 env: SLACK_BOT_TOKEN: ${{ secrets.QA_SLACK_API_KEY }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d33c35347ac..d90139e5292 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -11,34 +11,68 @@ on: - cron: '23 19 * * 4' jobs: - analyze: - name: Analyze ${{ matrix.language }} + filter: + name: Detect Changes + permissions: + pull-requests: read + outputs: + should-run-go: ${{ steps.changes.outputs.go-changes == 'true' || steps.changes.outputs.workflow-changes == 'true' || github.event == 'schedule' }} + should-run-js: ${{ steps.changes.outputs.js-changes == 'true' || steps.changes.outputs.workflow-changes == 'true' || github.event == 'schedule' }} runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v4.2.1 + with: + repository: smartcontractkit/chainlink + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: changes + with: + filters: | + go-changes: + - '**/*.go' + - '**/go.mod' + - '**/go.sum' + js-changes: + - '**/package.json' + - '**/pnpm-lock.yaml' + - '**/*.js' + - '**/*.ts' + workflow-changes: + - '.github/workflows/codeql-analysis.yml' + analyze: + needs: filter + name: Analyze ${{ matrix.type.language }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - language: ['go', 'javascript'] - + type: + - language: 'go' + should-run: ${{ needs.filter.outputs.should-run-go }} + - language: 'javascript' + should-run: ${{ needs.filter.outputs.should-run-js }} steps: - name: Checkout repository uses: actions/checkout@v4.2.1 - name: Set up Go - if: ${{ matrix.language == 'go' }} + if: ${{ matrix.type.language == 'go' && matrix.type.should-run == 'true' }} uses: ./.github/actions/setup-go with: go-version-file: 'go.mod' only-modules: 'true' - name: Touching core/web/assets/index.html - if: ${{ matrix.language == 'go' }} + if: ${{ matrix.type.language == 'go' && matrix.type.should-run == 'true' }} run: mkdir -p core/web/assets && touch core/web/assets/index.html - name: Initialize CodeQL + if: ${{ matrix.type.should-run == 'true' }} uses: github/codeql-action/init@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # codeql-bundle-v2.16.1 with: - languages: ${{ matrix.language }} + languages: ${{ matrix.type.language }} - name: Perform CodeQL Analysis + if: ${{ matrix.type.should-run == 'true' }} uses: github/codeql-action/analyze@65c74964a9ed8c44ed9f19d4bbc5757a6a8e9ab9 # codeql-bundle-v2.16.1 diff --git a/.github/workflows/find-new-flaky-tests.yml b/.github/workflows/find-new-flaky-tests.yml index bbae987f948..fb3676b30c8 100644 --- a/.github/workflows/find-new-flaky-tests.yml +++ b/.github/workflows/find-new-flaky-tests.yml @@ -68,6 +68,8 @@ jobs: workflow_id: ${{ steps.gen_id.outputs.workflow_id }} changed_test_files: ${{ steps.find-changed-test-files.outputs.test_files }} affected_test_packages: ${{ steps.find-tests.outputs.packages }} + git_head_sha: ${{ steps.get_commit_sha.outputs.git_head_sha }} + git_head_short_sha: ${{ steps.get_commit_sha.outputs.git_head_short_sha }} steps: - name: Checkout repository uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 @@ -75,14 +77,23 @@ jobs: fetch-depth: 0 ref: ${{ env.GIT_HEAD_REF }} + - name: Get commit SHA + id: get_commit_sha + run: | + git_head_sha=$(git rev-parse HEAD) + git_head_short_sha=$(git rev-parse --short HEAD) + echo "git_head_sha=$git_head_sha" >> $GITHUB_OUTPUT + echo "git_head_short_sha=$git_head_short_sha" >> $GITHUB_OUTPUT + - name: Set up Go 1.21.9 uses: actions/setup-go@v5.0.2 with: go-version: '1.21.9' + cache: false - name: Install flakeguard shell: bash - run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@2a8e6cddfd618291855b210c3a9d7810e8ca99d6 + run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@cb4c307f6f0a79a20097129cda7c151d8c5b5d28 - name: Find new or updated test packages id: find-tests @@ -177,6 +188,8 @@ jobs: prod: "true" - name: Setup Go uses: ./.github/actions/setup-go + with: + restore-build-cache-only: "true" - name: Setup Solana uses: ./.github/actions/setup-solana - name: Setup wasmd @@ -220,7 +233,7 @@ jobs: - name: Install flakeguard shell: bash - run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@2a8e6cddfd618291855b210c3a9d7810e8ca99d6 + run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard@cb4c307f6f0a79a20097129cda7c151d8c5b5d28 - name: Run tests with flakeguard shell: bash @@ -234,7 +247,7 @@ jobs: with: name: test-result-${{ needs.find-tests.outputs.workflow_id }}-${{ steps.gen_id.outputs.id }} path: test-result.json - retention-days: 1 + retention-days: 7 report: needs: [find-tests, run-tests] @@ -248,7 +261,7 @@ jobs: id: set_project_path_pretty run: | if [ "${{ inputs.projectPath }}" = "." ]; then - echo "path=root/go.mod" >> $GITHUB_OUTPUT + echo "path=./go.mod" >> $GITHUB_OUTPUT else echo "path=${{ inputs.projectPath }}/go.mod" >> $GITHUB_OUTPUT fi @@ -282,12 +295,19 @@ jobs: echo "failed_tests_count=0" >> "$GITHUB_OUTPUT" fi + - name: Calculate Flakiness Threshold Percentage + id: calculate_threshold + run: | + threshold_percentage=$(echo '${{ inputs.runThreshold }}' | awk '{printf "%.0f", $1 * 100}') + echo "threshold_percentage=$threshold_percentage" >> $GITHUB_OUTPUT + - name: Upload Failed Test Results as Artifact if: ${{ fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 }} uses: actions/upload-artifact@v4.4.3 with: name: failed_tests.json path: test_results/failed_tests.json + retention-days: 7 - name: Create ASCII table with failed test results if: ${{ fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 }} @@ -307,8 +327,7 @@ jobs: run: | echo "## Flaky Test Detection Summary" >> $GITHUB_STEP_SUMMARY echo "### Comparative Test Analysis" >> $GITHUB_STEP_SUMMARY - - echo "Checked changes between \`${{ inputs.baseRef }}\` and \`${{ env.GIT_HEAD_REF }}\` for \`${{ steps.set_project_path_pretty.outputs.path }}\` project. See all changes [here](${{ inputs.repoUrl }}/compare/${{ inputs.baseRef }}...${{ inputs.headRef }}#files_bucket)." >> $GITHUB_STEP_SUMMARY + echo "Checked changes between \`${{ inputs.baseRef }}\` and \`${{ env.GIT_HEAD_REF }}\` for ${{ steps.set_project_path_pretty.outputs.path }} project. See all changes [here](${{ inputs.repoUrl }}/compare/${{ inputs.baseRef }}...${{ needs.find-tests.outputs.git_head_sha }}#files_bucket)." >> $GITHUB_STEP_SUMMARY - name: Append Changed Test Files to GitHub Summary if: ${{ needs.find-tests.outputs.changed_test_files != '' && inputs.findByTestFilesDiff && !inputs.findByAffectedPackages }} @@ -332,6 +351,15 @@ jobs: done echo '```' >> $GITHUB_STEP_SUMMARY + - name: Read Failed Tests File + if: ${{ fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 }} + id: read_failed_tests + run: | + file_content=$(cat test_results/failed_tests_ascii.txt) + echo "failed_tests_content<> $GITHUB_OUTPUT + echo "$file_content" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Append Failed Tests to GitHub Summary if: ${{ fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 }} run: | @@ -367,6 +395,43 @@ jobs: echo "### No Tests To Execute" >> $GITHUB_STEP_SUMMARY echo "No updated or new tests found for \`${{ steps.set_project_path_pretty.outputs.path }}\` project. The flaky detector will not run." >> $GITHUB_STEP_SUMMARY + - name: Post comment on PR if flaky tests found + if: ${{ fromJson(steps.set_test_results.outputs.failed_tests_count) > 0 && github.event_name == 'pull_request' }} + uses: actions/github-script@v7 + env: + MESSAGE_BODY_1: '### Flaky Test Detector for `${{ steps.set_project_path_pretty.outputs.path }}` project has failed :x:' + MESSAGE_BODY_2: 'Ran new or updated tests between `${{ inputs.baseRef }}` and ${{ needs.find-tests.outputs.git_head_sha }} (`${{ env.GIT_HEAD_REF }}`).' + MESSAGE_BODY_3: ${{ format('[View Flaky Detector Details]({0}/{1}/actions/runs/{2}) | [Compare Changes]({3}/compare/{4}...{5}#files_bucket)', github.server_url, github.repository, github.run_id, inputs.repoUrl, github.base_ref, needs.find-tests.outputs.git_head_sha) }} + MESSAGE_BODY_4: '#### Failed Tests' + MESSAGE_BODY_5: 'Ran ${{ steps.set_test_results.outputs.all_tests_count }} tests in total for all affected test packages. Below are the tests identified as flaky, with a pass ratio lower than the ${{ steps.calculate_threshold.outputs.threshold_percentage }}% threshold:' + MESSAGE_BODY_6: '```' + MESSAGE_BODY_7: '${{ steps.read_failed_tests.outputs.failed_tests_content }}' + MESSAGE_BODY_8: '```' + with: + script: | + const prNumber = context.payload.pull_request.number; + + const commentBody = `${process.env.MESSAGE_BODY_1} + + ${process.env.MESSAGE_BODY_2} + + ${process.env.MESSAGE_BODY_3} + + ${process.env.MESSAGE_BODY_4} + + ${process.env.MESSAGE_BODY_5} + + ${process.env.MESSAGE_BODY_6} + ${process.env.MESSAGE_BODY_7} + ${process.env.MESSAGE_BODY_8}`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: commentBody + }); + - name: Send Slack message uses: slackapi/slack-github-action@6c661ce58804a1a20f6dc5fbee7f0381b469e001 # v1.25.0 if: ${{ inputs.slackNotificationAfterTestsChannelId != '' && fromJson(steps.set_test_results.outputs.all_tests_count) > 0 }} @@ -385,23 +450,23 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "Flaky Test Detection for ${{ steps.set_project_path_pretty.outputs.path }} - ${{ contains(join(needs.*.result, ','), 'failure') && 'Found flaky tests :x:' || contains(join(needs.*.result, ','), 'cancelled') && 'Cancelled :warning:' || format('Ran {0} tests and found no flakes :white_check_mark:', steps.set_test_results.outputs.all_tests_count) }}" + "text": "Flaky Test Detector for ${{ steps.set_project_path_pretty.outputs.path }} project - ${{ contains(join(needs.*.result, ','), 'failure') && 'Failed :x:' || contains(join(needs.*.result, ','), 'cancelled') && 'Was cancelled :warning:' || 'Passed :white_check_mark:' }}" } }, { "type": "section", "text": { "type": "mrkdwn", - "text": "Checked changes between `${{ inputs.baseRef }}` and `${{ env.GIT_HEAD_REF }}`" + "text": "Ran changed tests between `${{ inputs.baseRef }}` and `${{ needs.find-tests.outputs.git_head_short_sha }}` (`${{ env.GIT_HEAD_REF }}`)." } }, { "type": "section", "text": { "type": "mrkdwn", - "text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Details>" + "text": "${{ format('<{0}/{1}/actions/runs/{2}|View Flaky Detector Details> | <{3}/compare/{4}...{5}#files_bucket|Compare Changes>{6}', github.server_url, github.repository, github.run_id, inputs.repoUrl, inputs.baseRef, needs.find-tests.outputs.git_head_sha, github.event_name == 'pull_request' && format(' | <{0}|View PR>', github.event.pull_request.html_url) || '') }}" } - } + } ] } ] diff --git a/.github/workflows/solidity.yml b/.github/workflows/solidity.yml index 722c982b562..fb826b0f185 100644 --- a/.github/workflows/solidity.yml +++ b/.github/workflows/solidity.yml @@ -118,6 +118,8 @@ jobs: run: pnpm lint - name: Run solhint run: pnpm solhint + - name: Run solhint on tests + run: pnpm solhint-test prettier: defaults: diff --git a/contracts/.changeset/chatty-feet-clean.md b/contracts/.changeset/chatty-feet-clean.md deleted file mode 100644 index 161bfee8acd..00000000000 --- a/contracts/.changeset/chatty-feet-clean.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal address security vulnerabilities around updating nodes and node operators on capabilities registry diff --git a/contracts/.changeset/chilled-melons-warn.md b/contracts/.changeset/chilled-melons-warn.md deleted file mode 100644 index f94192bb60c..00000000000 --- a/contracts/.changeset/chilled-melons-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal index don ID in ConfigSet event diff --git a/contracts/.changeset/config.json b/contracts/.changeset/config.json index 8205542a708..c5f760594dc 100644 --- a/contracts/.changeset/config.json +++ b/contracts/.changeset/config.json @@ -1,7 +1,7 @@ { "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", "changelog": [ - "@changesets/changelog-github", + "../../.changeset/changelog-generator.js", { "repo": "smartcontractkit/chainlink" } diff --git a/contracts/.changeset/eight-timers-sip.md b/contracts/.changeset/eight-timers-sip.md deleted file mode 100644 index 3f81544e34f..00000000000 --- a/contracts/.changeset/eight-timers-sip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -Add new channel definitions config store contract for parallel compositions #added diff --git a/contracts/.changeset/eighty-ways-vanish.md b/contracts/.changeset/eighty-ways-vanish.md deleted file mode 100644 index 3a48ca4e710..00000000000 --- a/contracts/.changeset/eighty-ways-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -Publish a comment in PR mentioning the actor and informing her about avilability of Slither reports. diff --git a/contracts/.changeset/empty-ants-suffer.md b/contracts/.changeset/empty-ants-suffer.md deleted file mode 100644 index 8270da10f1e..00000000000 --- a/contracts/.changeset/empty-ants-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -Added updateFromPrevious method to Functions ToS contract diff --git a/contracts/.changeset/few-parents-punch.md b/contracts/.changeset/few-parents-punch.md deleted file mode 100644 index 88a885606bf..00000000000 --- a/contracts/.changeset/few-parents-punch.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -update comments - - -PR issue: SHIP-3557 diff --git a/contracts/.changeset/flat-turkeys-rule.md b/contracts/.changeset/flat-turkeys-rule.md deleted file mode 100644 index 2dedbe653ed..00000000000 --- a/contracts/.changeset/flat-turkeys-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal merge ccip contracts diff --git a/contracts/.changeset/fluffy-papayas-chew.md b/contracts/.changeset/fluffy-papayas-chew.md deleted file mode 100644 index aa7b145e8f6..00000000000 --- a/contracts/.changeset/fluffy-papayas-chew.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@chainlink/contracts': minor ---- - -#internal Change Chain Reader testing contract Triggered event for easier testing of filtering by non indexed evm data. - - -PR issue: BCFR-203 diff --git a/contracts/.changeset/forty-radios-brush.md b/contracts/.changeset/forty-radios-brush.md deleted file mode 100644 index 5356ffedb1b..00000000000 --- a/contracts/.changeset/forty-radios-brush.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -Add Configurator contract - - -PR issue: MERC-6185 diff --git a/contracts/.changeset/funny-meals-remember.md b/contracts/.changeset/funny-meals-remember.md deleted file mode 100644 index cb40a347e45..00000000000 --- a/contracts/.changeset/funny-meals-remember.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': minor ---- - -#internal Modify Contract Reader tester helper BCFR-912 diff --git a/contracts/.changeset/heavy-balloons-cheat.md b/contracts/.changeset/heavy-balloons-cheat.md deleted file mode 100644 index a6cc994c8d3..00000000000 --- a/contracts/.changeset/heavy-balloons-cheat.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#added Add ZKSync L2EP SequencerUptimeFeed contract -#added Add ZKSync L2EP Validator contract - - -PR issue: SHIP-3004 diff --git a/contracts/.changeset/hip-cherries-marry.md b/contracts/.changeset/hip-cherries-marry.md deleted file mode 100644 index d5928a5dbf2..00000000000 --- a/contracts/.changeset/hip-cherries-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': minor ---- - -Add encryptionPublicKey to CapabilitiesRegistry.sol diff --git a/contracts/.changeset/itchy-deers-deny.md b/contracts/.changeset/itchy-deers-deny.md deleted file mode 100644 index 697f451cf56..00000000000 --- a/contracts/.changeset/itchy-deers-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -More comprehensive & product-scoped Solidity Foundry pipeline diff --git a/contracts/.changeset/itchy-turtles-agree.md b/contracts/.changeset/itchy-turtles-agree.md deleted file mode 100644 index 930ab850d9b..00000000000 --- a/contracts/.changeset/itchy-turtles-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': minor ---- - -#internal Add an event with indexed topics that get hashed to Chain Reader Tester contract. diff --git a/contracts/.changeset/loud-lobsters-guess.md b/contracts/.changeset/loud-lobsters-guess.md deleted file mode 100644 index e470267e4e4..00000000000 --- a/contracts/.changeset/loud-lobsters-guess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -auto: create a replication from v2_3 to v2_3_zksync diff --git a/contracts/.changeset/mean-zoos-fly.md b/contracts/.changeset/mean-zoos-fly.md deleted file mode 100644 index 72eb98198d0..00000000000 --- a/contracts/.changeset/mean-zoos-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -add OZ v0.5 contracts diff --git a/contracts/.changeset/metal-ducks-hunt.md b/contracts/.changeset/metal-ducks-hunt.md new file mode 100644 index 00000000000..caba4819256 --- /dev/null +++ b/contracts/.changeset/metal-ducks-hunt.md @@ -0,0 +1,10 @@ +--- +'@chainlink/contracts': patch +--- + +#feature adds OZ AccessControl support to the registry module + + +PR issue: CCIP-4105 + +Solidity Review issue: CCIP-3966 \ No newline at end of file diff --git a/contracts/.changeset/nasty-llamas-prove.md b/contracts/.changeset/nasty-llamas-prove.md deleted file mode 100644 index dd344676808..00000000000 --- a/contracts/.changeset/nasty-llamas-prove.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -DEVSVCS-147: add a reentrancy guard for balance monitor - -PR issue: DEVSVCS-147 diff --git a/contracts/.changeset/nice-planets-share.md b/contracts/.changeset/nice-planets-share.md deleted file mode 100644 index a8af56ac613..00000000000 --- a/contracts/.changeset/nice-planets-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': minor ---- - -EnumerableMap Library for an Address to Bytes mapping diff --git a/contracts/.changeset/orange-plums-fold.md b/contracts/.changeset/orange-plums-fold.md deleted file mode 100644 index b6cf88c81b3..00000000000 --- a/contracts/.changeset/orange-plums-fold.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -Adding USDCReaderTester contract for CCIP integration tests #internal - - -CCIP-2881 \ No newline at end of file diff --git a/contracts/.changeset/polite-masks-jog.md b/contracts/.changeset/polite-masks-jog.md deleted file mode 100644 index 93fba83b558..00000000000 --- a/contracts/.changeset/polite-masks-jog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal diff --git a/contracts/.changeset/proud-pears-tie.md b/contracts/.changeset/proud-pears-tie.md deleted file mode 100644 index 93fba83b558..00000000000 --- a/contracts/.changeset/proud-pears-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal diff --git a/contracts/.changeset/quick-olives-accept.md b/contracts/.changeset/quick-olives-accept.md deleted file mode 100644 index 31c59983e47..00000000000 --- a/contracts/.changeset/quick-olives-accept.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@chainlink/contracts': minor ---- - -#updated move latest ccip contracts code from ccip repo to chainlink repo - -PR issue: CCIP-2946 diff --git a/contracts/.changeset/few-camels-tan.md b/contracts/.changeset/quick-pans-tie.md similarity index 52% rename from contracts/.changeset/few-camels-tan.md rename to contracts/.changeset/quick-pans-tie.md index ca2574171d5..5c96ea701dc 100644 --- a/contracts/.changeset/few-camels-tan.md +++ b/contracts/.changeset/quick-pans-tie.md @@ -2,4 +2,4 @@ '@chainlink/contracts': patch --- -bump dependencies +#internal Enable Solhint for tests diff --git a/contracts/.changeset/quiet-lamps-walk.md b/contracts/.changeset/quiet-lamps-walk.md deleted file mode 100644 index 93fba83b558..00000000000 --- a/contracts/.changeset/quiet-lamps-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal diff --git a/contracts/.changeset/quiet-moles-retire.md b/contracts/.changeset/quiet-moles-retire.md deleted file mode 100644 index 6351f36a16c..00000000000 --- a/contracts/.changeset/quiet-moles-retire.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -Use reusable workflow for Solidity Artifacts pipeline, move some actions to chainlink-github-actions repository - - -PR issue: TT-1693 diff --git a/contracts/.changeset/rich-lamps-do.md b/contracts/.changeset/rich-lamps-do.md deleted file mode 100644 index 3f432d3de6b..00000000000 --- a/contracts/.changeset/rich-lamps-do.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -update zksync automation contract version and small fixes diff --git a/contracts/.changeset/seven-donkeys-live.md b/contracts/.changeset/seven-donkeys-live.md deleted file mode 100644 index 141588f5b9f..00000000000 --- a/contracts/.changeset/seven-donkeys-live.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -improve cron contracts imports diff --git a/contracts/.changeset/sharp-avocados-arrive.md b/contracts/.changeset/sharp-avocados-arrive.md deleted file mode 100644 index 6bfd0524a88..00000000000 --- a/contracts/.changeset/sharp-avocados-arrive.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal remove CCIP 1.5 - - -PR issue: CCIP-3748 \ No newline at end of file diff --git a/contracts/.changeset/silent-houses-join.md b/contracts/.changeset/silent-houses-join.md deleted file mode 100644 index 4138756c78a..00000000000 --- a/contracts/.changeset/silent-houses-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -Enable rotating encryptionPublicKey in CapabilitiesRegistry contract diff --git a/contracts/.changeset/silver-pots-cover.md b/contracts/.changeset/silver-pots-cover.md deleted file mode 100644 index 93fba83b558..00000000000 --- a/contracts/.changeset/silver-pots-cover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal diff --git a/contracts/.changeset/slimy-pens-listen.md b/contracts/.changeset/slimy-pens-listen.md deleted file mode 100644 index ff81d222378..00000000000 --- a/contracts/.changeset/slimy-pens-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal prevent editing whether or not a DON accepts workflows diff --git a/contracts/.changeset/strong-boats-brake.md b/contracts/.changeset/strong-boats-brake.md deleted file mode 100644 index 4f1b780565f..00000000000 --- a/contracts/.changeset/strong-boats-brake.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -Add linkage between PR and Jira's Solidity Review issue - - -PR issue: TT-1624 diff --git a/contracts/.changeset/tall-donkeys-flow.md b/contracts/.changeset/tall-donkeys-flow.md deleted file mode 100644 index 3753880baeb..00000000000 --- a/contracts/.changeset/tall-donkeys-flow.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@chainlink/contracts': minor ---- - -#internal Updated ChainReaderTester to include dynamic and static nested structs in TestStruct - - -PR issue: BCFR-44 - -Solidity Review issue: BCFR-957 \ No newline at end of file diff --git a/contracts/.changeset/tender-comics-check.md b/contracts/.changeset/tender-comics-check.md deleted file mode 100644 index 6ea48d92e4e..00000000000 --- a/contracts/.changeset/tender-comics-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal prevent reentrancy when configuring DON in capabilities registry diff --git a/contracts/.changeset/thirty-lamps-reply.md b/contracts/.changeset/thirty-lamps-reply.md deleted file mode 100644 index d8bcf8d4e83..00000000000 --- a/contracts/.changeset/thirty-lamps-reply.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -implement an auto registry for zksync with no forwarder interface change diff --git a/contracts/.changeset/three-stingrays-compete.md b/contracts/.changeset/three-stingrays-compete.md deleted file mode 100644 index 613b2784657..00000000000 --- a/contracts/.changeset/three-stingrays-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': minor ---- - -add ccip contracts to the repo diff --git a/contracts/.changeset/tidy-kings-itch.md b/contracts/.changeset/tidy-kings-itch.md deleted file mode 100644 index e493a6cc551..00000000000 --- a/contracts/.changeset/tidy-kings-itch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal minor keystone improvements diff --git a/contracts/.changeset/twenty-pears-battle.md b/contracts/.changeset/twenty-pears-battle.md deleted file mode 100644 index 5a204f68891..00000000000 --- a/contracts/.changeset/twenty-pears-battle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -update token transfer logic in weth9 diff --git a/contracts/.changeset/unlucky-rocks-marry.md b/contracts/.changeset/unlucky-rocks-marry.md deleted file mode 100644 index 723bb1e130a..00000000000 --- a/contracts/.changeset/unlucky-rocks-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@chainlink/contracts': patch ---- - -#internal use ERC165Checker diff --git a/contracts/.solhint-test.json b/contracts/.solhint-test.json new file mode 100644 index 00000000000..e26b18b597b --- /dev/null +++ b/contracts/.solhint-test.json @@ -0,0 +1,50 @@ +{ + "extends": "solhint:recommended", + "plugins": ["prettier", "chainlink-solidity"], + "rules": { + "compiler-version": ["off", "^0.8.0"], + "const-name-snakecase": "off", + "constructor-syntax": "error", + "var-name-mixedcase": "off", + "func-named-parameters": "off", + "immutable-vars-naming": "off", + "no-inline-assembly": "off", + "contract-name-camelcase": "off", + "one-contract-per-file": "off", + "avoid-low-level-calls": "off", + "reentrancy": "off", + "func-name-mixedcase": "off", + "no-unused-import": "error", + "gas-struct-packing": "warn", + "interface-starts-with-i": "warn", + "func-visibility": [ + "error", + { + "ignoreConstructors": true + } + ], + "not-rely-on-time": "off", + "prettier/prettier": [ + "off", + { + "endOfLine": "auto" + } + ], + "no-empty-blocks": "off", + "quotes": ["error", "double"], + "reason-string": [ + "warn", + { + "maxLength": 64 + } + ], + "chainlink-solidity/prefix-internal-functions-with-underscore": "warn", + "chainlink-solidity/prefix-private-functions-with-underscore": "warn", + "chainlink-solidity/prefix-storage-variables-with-s-underscore": "warn", + "chainlink-solidity/prefix-immutable-variables-with-i": "warn", + "chainlink-solidity/all-caps-constant-storage-variables": "warn", + "chainlink-solidity/no-hardhat-imports": "warn", + "chainlink-solidity/inherited-constructor-args-not-in-contract-definition": "warn", + "chainlink-solidity/explicit-returns": "warn" + } +} diff --git a/contracts/.solhintignore b/contracts/.solhintignore index 81291fe0871..7ae5b10d150 100644 --- a/contracts/.solhintignore +++ b/contracts/.solhintignore @@ -1,3 +1,6 @@ +# Test files run with a different solhint ruleset, ignore them here. +./**/*.t.sol + # Ignore frozen Automation code ./src/v0.8/automation/v1_2 ./src/v0.8/automation/interfaces/v1_2 @@ -29,13 +32,11 @@ # Ignore Functions v1.0.0 code that was frozen after audit ./src/v0.8/functions/v1_0_0 -# Ignore tests, this should not be the long term plan but is OK in the short term -./src/v0.8/**/*.t.sol -./src/v0.8/mocks -./src/v0.8/tests -./src/v0.8/llo-feeds/test +# Test helpers ./src/v0.8/vrf/testhelpers +./src/v0.8/tests ./src/v0.8/functions/tests +./src/v0.8/mocks/ # Always ignore vendor ./src/v0.8/vendor diff --git a/contracts/.solhintignore-test b/contracts/.solhintignore-test new file mode 100644 index 00000000000..acaca4fe1e4 --- /dev/null +++ b/contracts/.solhintignore-test @@ -0,0 +1,26 @@ +./src/v0.8/automation/ +./src/v0.8/vrf/ +./src/v0.8/mocks +./src/v0.8/tests +./src/v0.8/llo-feeds/ +./src/v0.8/automation/ +./src/v0.8/transmission/ +./src/v0.8/l2ep/ +./src/v0.8/shared/ +./src/v0.8/operatorforwarder/ +./src/v0.8/functions/ +./src/v0.8/liquiditymanager/ +./src/v0.8/keystone/ +./src/v0.8/llo-feeds/ + + +# Ignore Functions v1.0.0 code that was frozen after audit +./src/v0.8/functions/v1_0_0 + + +# Always ignore vendor +./src/v0.8/vendor +./node_modules/ + +# Ignore tweaked vendored contracts +./src/v0.8/shared/enumerable/EnumerableSetWithBytes16.sol \ No newline at end of file diff --git a/contracts/CHANGELOG.md b/contracts/CHANGELOG.md index 01a9dd4b6c4..5e2b0ee73a0 100644 --- a/contracts/CHANGELOG.md +++ b/contracts/CHANGELOG.md @@ -1,5 +1,52 @@ # @chainlink/contracts +## 1.3.0 - 2024-10-21 + +### Minor Changes + +- [#14207](https://github.com/smartcontractkit/chainlink/pull/14207) [`328b62a`](https://github.com/smartcontractkit/chainlink/commit/328b62ae5067619e59da42f6db6703d3b327f1a2) Thanks [@ilija42](https://github.com/ilija42)! - #internal Change Chain Reader testing contract Triggered event for easier testing of filtering by non indexed evm data. +- [#14622](https://github.com/smartcontractkit/chainlink/pull/14622) [`c654322`](https://github.com/smartcontractkit/chainlink/commit/c654322acea7da8e6bd84a8a045690002f1f172d) Thanks [@ilija42](https://github.com/ilija42)! - #internal Modify Contract Reader tester helper BCFR-912 +- [#14709](https://github.com/smartcontractkit/chainlink/pull/14709) [`1560aa9`](https://github.com/smartcontractkit/chainlink/commit/1560aa9167a812abe3a8370c033b3290dcbcb261) Thanks [@KuphJr](https://github.com/KuphJr)! - Add encryptionPublicKey to CapabilitiesRegistry.sol +- [#14016](https://github.com/smartcontractkit/chainlink/pull/14016) [`8b9f2b6`](https://github.com/smartcontractkit/chainlink/commit/8b9f2b6b9098e8ec2368773368239106d066e4e3) Thanks [@ilija42](https://github.com/ilija42)! - #internal Add an event with indexed topics that get hashed to Chain Reader Tester contract. +- [#14012](https://github.com/smartcontractkit/chainlink/pull/14012) [`518cc28`](https://github.com/smartcontractkit/chainlink/commit/518cc281b14727c26cd7fcb9db882b45837d443a) Thanks [@defistar](https://github.com/defistar)! - EnumerableMap Library for an Address to Bytes mapping +- [#14266](https://github.com/smartcontractkit/chainlink/pull/14266) [`c323e0d`](https://github.com/smartcontractkit/chainlink/commit/c323e0d600c659a4ea584dbae0a0db187afd51eb) Thanks [@asoliman92](https://github.com/asoliman92)! - #updated move latest ccip contracts code from ccip repo to chainlink repo +- [#14511](https://github.com/smartcontractkit/chainlink/pull/14511) [`8fa9a67`](https://github.com/smartcontractkit/chainlink/commit/8fa9a67dfc130feab290860f0b7bf860ddc86bb3) Thanks [@silaslenihan](https://github.com/silaslenihan)! - #internal Updated ChainReaderTester to include dynamic and static nested structs in TestStruct +- [#13941](https://github.com/smartcontractkit/chainlink/pull/13941) [`9e74eee`](https://github.com/smartcontractkit/chainlink/commit/9e74eee9d415b386db33bdf2dd44facc82cd3551) Thanks [@RensR](https://github.com/RensR)! - add ccip contracts to the repo + +### Patch Changes + +- [#13937](https://github.com/smartcontractkit/chainlink/pull/13937) [`27d9c71`](https://github.com/smartcontractkit/chainlink/commit/27d9c71b196961666de87bc3128d31f3c22fb3fa) Thanks [@cds95](https://github.com/cds95)! - #internal address security vulnerabilities around updating nodes and node operators on capabilities registry +- [#14241](https://github.com/smartcontractkit/chainlink/pull/14241) [`7c248e7`](https://github.com/smartcontractkit/chainlink/commit/7c248e7c466ad278b0024e4ac743813009b16805) Thanks [@cds95](https://github.com/cds95)! - #internal index don ID in ConfigSet event +- [#13780](https://github.com/smartcontractkit/chainlink/pull/13780) [`af335c1`](https://github.com/smartcontractkit/chainlink/commit/af335c1a522769c8c29858d8d6510330af3204cf) Thanks [@samsondav](https://github.com/samsondav)! - Add new channel definitions config store contract for parallel compositions #added +- [#14198](https://github.com/smartcontractkit/chainlink/pull/14198) [`e452ee1`](https://github.com/smartcontractkit/chainlink/commit/e452ee1ddb520b86f827ac75cccdb0719e9f5335) Thanks [@Tofel](https://github.com/Tofel)! - Publish a comment in PR mentioning the actor and informing her about avilability of Slither reports. +- [#13795](https://github.com/smartcontractkit/chainlink/pull/13795) [`683a12e`](https://github.com/smartcontractkit/chainlink/commit/683a12e85e91628f240fe24f32b982b53ac30bd9) Thanks [@KuphJr](https://github.com/KuphJr)! - Added updateFromPrevious method to Functions ToS contract +- [#14093](https://github.com/smartcontractkit/chainlink/pull/14093) [`95ae744`](https://github.com/smartcontractkit/chainlink/commit/95ae74437c42699d27e1d37f66ca8ddef68ce58f) Thanks [@RensR](https://github.com/RensR)! - bump dependencies +- [#14371](https://github.com/smartcontractkit/chainlink/pull/14371) [`0efcf38`](https://github.com/smartcontractkit/chainlink/commit/0efcf380192837a64bbca946474866e8e1bdcec0) Thanks [@jlaveracll](https://github.com/jlaveracll)! - update comments +- [#14345](https://github.com/smartcontractkit/chainlink/pull/14345) [`c83c687`](https://github.com/smartcontractkit/chainlink/commit/c83c68735bdee6bbd8510733b7415797cd08ecbd) Thanks [@makramkd](https://github.com/makramkd)! - #internal merge ccip contracts +- [#14249](https://github.com/smartcontractkit/chainlink/pull/14249) [`e8c2453`](https://github.com/smartcontractkit/chainlink/commit/e8c2453e8581ed7ad83033d938567dcce8f6c5a5) Thanks [@samsondav](https://github.com/samsondav)! - Add Configurator contract +- [#14245](https://github.com/smartcontractkit/chainlink/pull/14245) [`39a6f91`](https://github.com/smartcontractkit/chainlink/commit/39a6f91fcaba4c51e41a33afc3cdb572af8343dd) Thanks [@jlaveracll](https://github.com/jlaveracll)! - #added Add ZKSync L2EP SequencerUptimeFeed contract #added Add ZKSync L2EP Validator contract +- [#13986](https://github.com/smartcontractkit/chainlink/pull/13986) [`4b91691`](https://github.com/smartcontractkit/chainlink/commit/4b9169131cd44d6cb4f00dae2b33e49626af5f7d) Thanks [@Tofel](https://github.com/Tofel)! - More comprehensive & product-scoped Solidity Foundry pipeline +- [#14035](https://github.com/smartcontractkit/chainlink/pull/14035) [`215277f`](https://github.com/smartcontractkit/chainlink/commit/215277f9e041d18dc5686c697e6959d5edaaf346) Thanks [@FelixFan1992](https://github.com/FelixFan1992)! - auto: create a replication from v2_3 to v2_3_zksync +- [#14065](https://github.com/smartcontractkit/chainlink/pull/14065) [`499a677`](https://github.com/smartcontractkit/chainlink/commit/499a67705ac7ea525685c4a064ff4aa52b08fa44) Thanks [@RyanRHall](https://github.com/RyanRHall)! - add OZ v0.5 contracts +- [#14108](https://github.com/smartcontractkit/chainlink/pull/14108) [`08194be`](https://github.com/smartcontractkit/chainlink/commit/08194beb46355135dedde89d37838f5da36f2cef) Thanks [@FelixFan1992](https://github.com/FelixFan1992)! - DEVSVCS-147: add a reentrancy guard for balance monitor +- [#14516](https://github.com/smartcontractkit/chainlink/pull/14516) [`0e32c07`](https://github.com/smartcontractkit/chainlink/commit/0e32c07d22973343e722a228ff1c3b1e8f9bc04e) Thanks [@mateusz-sekara](https://github.com/mateusz-sekara)! - Adding USDCReaderTester contract for CCIP integration tests #internal +- [#14017](https://github.com/smartcontractkit/chainlink/pull/14017) [`1257d33`](https://github.com/smartcontractkit/chainlink/commit/1257d33913d243c146bccbf4bda67a2bb1c7d5af) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal +- [#14543](https://github.com/smartcontractkit/chainlink/pull/14543) [`c4fa565`](https://github.com/smartcontractkit/chainlink/commit/c4fa565f5441bfa997907256e1990f9be276934d) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal +- [#14350](https://github.com/smartcontractkit/chainlink/pull/14350) [`070b272`](https://github.com/smartcontractkit/chainlink/commit/070b272f30054be6d4239d078121ca3b3054fc33) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal +- [#14436](https://github.com/smartcontractkit/chainlink/pull/14436) [`f74ac81`](https://github.com/smartcontractkit/chainlink/commit/f74ac81d5db7a89b04252938f4b5ff34e3f7bbbe) Thanks [@Tofel](https://github.com/Tofel)! - Use reusable workflow for Solidity Artifacts pipeline, move some actions to chainlink-github-actions repository +- [#14390](https://github.com/smartcontractkit/chainlink/pull/14390) [`f202bcf`](https://github.com/smartcontractkit/chainlink/commit/f202bcfe45648cc803b38650a7aaf6fecb91969d) Thanks [@FelixFan1992](https://github.com/FelixFan1992)! - update zksync automation contract version and small fixes +- [#13927](https://github.com/smartcontractkit/chainlink/pull/13927) [`ce90bc3`](https://github.com/smartcontractkit/chainlink/commit/ce90bc32f562e92af3d22c895446a963109c36e3) Thanks [@FelixFan1992](https://github.com/FelixFan1992)! - improve cron contracts imports +- [#14739](https://github.com/smartcontractkit/chainlink/pull/14739) [`4842271`](https://github.com/smartcontractkit/chainlink/commit/4842271b0f7054f5f1364c59d3d9da534c5d4f25) Thanks [@RensR](https://github.com/RensR)! - #internal remove CCIP 1.5 +- [#14760](https://github.com/smartcontractkit/chainlink/pull/14760) [`3af39c8`](https://github.com/smartcontractkit/chainlink/commit/3af39c803201461009ef63f709851fe6a24f0284) Thanks [@KuphJr](https://github.com/KuphJr)! - Enable rotating encryptionPublicKey in CapabilitiesRegistry contract +- [#13993](https://github.com/smartcontractkit/chainlink/pull/13993) [`f5e0bd6`](https://github.com/smartcontractkit/chainlink/commit/f5e0bd614a6c42d195c4ad74a10f7070970d01d5) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal +- [#14092](https://github.com/smartcontractkit/chainlink/pull/14092) [`3399dd6`](https://github.com/smartcontractkit/chainlink/commit/3399dd6d7fee12bd8d099b74397edcc4dc56c11d) Thanks [@cds95](https://github.com/cds95)! - #internal prevent editing whether or not a DON accepts workflows +- [#14521](https://github.com/smartcontractkit/chainlink/pull/14521) [`b4360c9`](https://github.com/smartcontractkit/chainlink/commit/b4360c9aece538e20ef688750adcd5a838729930) Thanks [@Tofel](https://github.com/Tofel)! - Add linkage between PR and Jira's Solidity Review issue +- [#13970](https://github.com/smartcontractkit/chainlink/pull/13970) [`cefbb09`](https://github.com/smartcontractkit/chainlink/commit/cefbb09797249309ac18e4ef81147e30f7c24360) Thanks [@cds95](https://github.com/cds95)! - #internal prevent reentrancy when configuring DON in capabilities registry +- [#14037](https://github.com/smartcontractkit/chainlink/pull/14037) [`9c240b6`](https://github.com/smartcontractkit/chainlink/commit/9c240b686753c72f94f8fb7e8c636483d5759963) Thanks [@FelixFan1992](https://github.com/FelixFan1992)! - implement an auto registry for zksync with no forwarder interface change +- [#14767](https://github.com/smartcontractkit/chainlink/pull/14767) [`a3b552f`](https://github.com/smartcontractkit/chainlink/commit/a3b552f0f87546c5250b544b5dd2a4d31b7a9b42) Thanks [@RensR](https://github.com/RensR)! - #internal minor keystone improvements +- [#14481](https://github.com/smartcontractkit/chainlink/pull/14481) [`1a5e591`](https://github.com/smartcontractkit/chainlink/commit/1a5e591875d5d478be65605ad5dc66e8bf8b915b) Thanks [@FelixFan1992](https://github.com/FelixFan1992)! - update token transfer logic in weth9 +- [#14231](https://github.com/smartcontractkit/chainlink/pull/14231) [`7a41ae7`](https://github.com/smartcontractkit/chainlink/commit/7a41ae73bcb5f5eb9ffbc4f25059dbbc236a7e8a) Thanks [@DeividasK](https://github.com/DeividasK)! - #internal use ERC165Checker + ## 1.2.0 - 2024-07-18 ### Minor Changes diff --git a/contracts/gas-snapshots/ccip.gas-snapshot b/contracts/gas-snapshots/ccip.gas-snapshot index 39bd88c732c..42621b196c7 100644 --- a/contracts/gas-snapshots/ccip.gas-snapshot +++ b/contracts/gas-snapshots/ccip.gas-snapshot @@ -308,24 +308,24 @@ MultiAggregateRateLimiter_getTokenBucket:test_TimeUnderflow_Revert() (gas: 15907 MultiAggregateRateLimiter_getTokenValue:test_GetTokenValue_Success() (gas: 17602) MultiAggregateRateLimiter_getTokenValue:test_NoTokenPrice_Reverts() (gas: 21630) MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageFromUnauthorizedCaller_Revert() (gas: 14636) -MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithDifferentTokensOnDifferentChains_Success() (gas: 210589) -MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithDisabledRateLimitToken_Success() (gas: 58469) -MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithNoTokens_Success() (gas: 17809) -MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitDisabled_Success() (gas: 45220) -MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitExceeded_Revert() (gas: 46488) -MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitReset_Success() (gas: 76929) -MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithTokensOnDifferentChains_Success() (gas: 308969) -MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithTokens_Success() (gas: 50654) -MultiAggregateRateLimiter_onOutboundMessage:test_RateLimitValueDifferentLanes_Success() (gas: 51305) -MultiAggregateRateLimiter_onOutboundMessage:test_ValidateMessageWithNoTokens_Success() (gas: 19393) +MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithDifferentTokensOnDifferentChains_Success() (gas: 210571) +MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithDisabledRateLimitToken_Success() (gas: 58451) +MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithNoTokens_Success() (gas: 17791) +MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitDisabled_Success() (gas: 45202) +MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitExceeded_Revert() (gas: 46470) +MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithRateLimitReset_Success() (gas: 76911) +MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithTokensOnDifferentChains_Success() (gas: 308951) +MultiAggregateRateLimiter_onInboundMessage:test_ValidateMessageWithTokens_Success() (gas: 50636) +MultiAggregateRateLimiter_onOutboundMessage:test_RateLimitValueDifferentLanes_Success() (gas: 51287) +MultiAggregateRateLimiter_onOutboundMessage:test_ValidateMessageWithNoTokens_Success() (gas: 19375) MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageFromUnauthorizedCaller_Revert() (gas: 15914) -MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithDifferentTokensOnDifferentChains_Success() (gas: 210309) -MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithDisabledRateLimitToken_Success() (gas: 60262) -MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithRateLimitDisabled_Success() (gas: 47043) -MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithRateLimitExceeded_Revert() (gas: 48279) -MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithRateLimitReset_Success() (gas: 77936) -MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithTokensOnDifferentChains_Success() (gas: 308915) -MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithTokens_Success() (gas: 52424) +MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithDifferentTokensOnDifferentChains_Success() (gas: 210291) +MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithDisabledRateLimitToken_Success() (gas: 60244) +MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithRateLimitDisabled_Success() (gas: 47025) +MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithRateLimitExceeded_Revert() (gas: 48261) +MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithRateLimitReset_Success() (gas: 77918) +MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithTokensOnDifferentChains_Success() (gas: 308897) +MultiAggregateRateLimiter_onOutboundMessage:test_onOutboundMessage_ValidateMessageWithTokens_Success() (gas: 52406) MultiAggregateRateLimiter_setFeeQuoter:test_OnlyOwner_Revert() (gas: 10967) MultiAggregateRateLimiter_setFeeQuoter:test_Owner_Success() (gas: 19190) MultiAggregateRateLimiter_setFeeQuoter:test_ZeroAddress_Revert() (gas: 10642) @@ -411,7 +411,7 @@ OffRamp_batchExecute:test_OutOfBoundsGasLimitsAccess_Revert() (gas: 187853) OffRamp_batchExecute:test_SingleReport_Success() (gas: 156555) OffRamp_batchExecute:test_Unhealthy_Success() (gas: 553993) OffRamp_batchExecute:test_ZeroReports_Revert() (gas: 10600) -OffRamp_ccipReceive:test_Reverts() (gas: 15407) +OffRamp_ccipReceive:test_RevertWhen_Always() (gas: 15407) OffRamp_commit:test_CommitOnRampMismatch_Revert() (gas: 92834) OffRamp_commit:test_FailedRMNVerification_Reverts() (gas: 63500) OffRamp_commit:test_InvalidIntervalMinLargerThanMax_Revert() (gas: 70146) @@ -623,8 +623,8 @@ 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: 303866) -RMNRemote_verify_withConfigSet:test_verify_UnexpectedSigner_reverts() (gas: 427512) +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) RateLimiter_constructor:test_Constructor_Success() (gas: 19806) RateLimiter_consume:test_AggregateValueMaxCapacityExceeded_Revert() (gas: 16042) @@ -640,11 +640,13 @@ RateLimiter_consume:test_TokenRateLimitReached_Revert() (gas: 24930) RateLimiter_currentTokenBucketState:test_CurrentTokenBucketState_Success() (gas: 38947) RateLimiter_currentTokenBucketState:test_Refill_Success() (gas: 46852) RateLimiter_setTokenBucketConfig:test_SetRateLimiterConfig_Success() (gas: 38509) -RegistryModuleOwnerCustom_constructor:test_constructor_Revert() (gas: 36033) -RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin:test_registerAdminViaGetCCIPAdmin_Revert() (gas: 19763) -RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin:test_registerAdminViaGetCCIPAdmin_Success() (gas: 130104) -RegistryModuleOwnerCustom_registerAdminViaOwner:test_registerAdminViaOwner_Revert() (gas: 19568) -RegistryModuleOwnerCustom_registerAdminViaOwner:test_registerAdminViaOwner_Success() (gas: 129908) +RegistryModuleOwnerCustom_constructor:test_constructor_Revert() (gas: 36107) +RegistryModuleOwnerCustom_registerAccessControlDefaultAdmin:test_registerAccessControlDefaultAdmin_Revert() (gas: 20200) +RegistryModuleOwnerCustom_registerAccessControlDefaultAdmin:test_registerAccessControlDefaultAdmin_Success() (gas: 130631) +RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin:test_registerAdminViaGetCCIPAdmin_Revert() (gas: 19797) +RegistryModuleOwnerCustom_registerAdminViaGetCCIPAdmin:test_registerAdminViaGetCCIPAdmin_Success() (gas: 130126) +RegistryModuleOwnerCustom_registerAdminViaOwner:test_registerAdminViaOwner_Revert() (gas: 19602) +RegistryModuleOwnerCustom_registerAdminViaOwner:test_registerAdminViaOwner_Success() (gas: 129930) Router_applyRampUpdates:test_OffRampMismatch_Revert() (gas: 89591) Router_applyRampUpdates:test_OffRampUpdatesWithRouting() (gas: 10749462) Router_applyRampUpdates:test_OnRampDisable() (gas: 56428) @@ -702,12 +704,12 @@ TokenAdminRegistry_setPool:test_setPool_ZeroAddressRemovesPool_Success() (gas: 3 TokenAdminRegistry_transferAdminRole:test_transferAdminRole_OnlyAdministrator_Revert() (gas: 18202) TokenAdminRegistry_transferAdminRole:test_transferAdminRole_Success() (gas: 49592) TokenPoolFactoryTests:test_TokenPoolFactory_Constructor_Revert() (gas: 1039441) -TokenPoolFactoryTests:test_createTokenPoolLockRelease_ExistingToken_predict_Success() (gas: 11497148) -TokenPoolFactoryTests:test_createTokenPool_BurnFromMintTokenPool_Success() (gas: 5833878) -TokenPoolFactoryTests:test_createTokenPool_ExistingRemoteToken_AndPredictPool_Success() (gas: 12127839) -TokenPoolFactoryTests:test_createTokenPool_WithNoExistingRemoteContracts_predict_Success() (gas: 12464532) -TokenPoolFactoryTests:test_createTokenPool_WithNoExistingTokenOnRemoteChain_Success() (gas: 5687016) -TokenPoolFactoryTests:test_createTokenPool_WithRemoteTokenAndRemotePool_Success() (gas: 5830403) +TokenPoolFactoryTests:test_createTokenPoolLockRelease_ExistingToken_predict_Success() (gas: 11571451) +TokenPoolFactoryTests:test_createTokenPool_BurnFromMintTokenPool_Success() (gas: 5833900) +TokenPoolFactoryTests:test_createTokenPool_ExistingRemoteToken_AndPredictPool_Success() (gas: 12202164) +TokenPoolFactoryTests:test_createTokenPool_WithNoExistingRemoteContracts_predict_Success() (gas: 12538879) +TokenPoolFactoryTests:test_createTokenPool_WithNoExistingTokenOnRemoteChain_Success() (gas: 5687038) +TokenPoolFactoryTests:test_createTokenPool_WithRemoteTokenAndRemotePool_Success() (gas: 5830425) TokenPoolWithAllowList_applyAllowListUpdates:test_AllowListNotEnabled_Revert() (gas: 1934078) TokenPoolWithAllowList_applyAllowListUpdates:test_OnlyOwner_Revert() (gas: 12119) TokenPoolWithAllowList_applyAllowListUpdates:test_SetAllowListSkipsZero_Success() (gas: 23567) diff --git a/contracts/package.json b/contracts/package.json index a421925e40b..731b695f636 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@chainlink/contracts", - "version": "1.2.0", + "version": "1.3.0", "description": "Chainlink smart contracts", "author": "Chainlink devs", "license": "MIT", @@ -18,7 +18,8 @@ "prepublishOnly": "pnpm compile && ./scripts/prepublish_generate_abi_folder", "publish-beta": "pnpm publish --tag beta", "publish-prod": "pnpm publish --tag latest", - "solhint": "solhint --max-warnings 0 \"./src/v0.8/**/*.sol\"" + "solhint": "solhint --max-warnings 0 \"./src/v0.8/**/*.sol\"", + "solhint-test": "solhint --config \".solhint-test.json\" --ignore-path \".solhintignore-test\" --max-warnings 0 \"./src/v0.8/**/*.sol\"" }, "files": [ "src/v0.8", @@ -80,8 +81,8 @@ "dependencies": { "@arbitrum/nitro-contracts": "1.1.1", "@arbitrum/token-bridge-contracts": "1.1.2", - "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "~2.27.8", + "@changesets/get-github-info": "^0.6.0", "@eth-optimism/contracts": "0.6.0", "@openzeppelin/contracts": "4.9.3", "@openzeppelin/contracts-upgradeable": "4.9.3", diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index 20fcd2e2eed..2ea91943b13 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -17,12 +17,12 @@ importers: '@arbitrum/token-bridge-contracts': specifier: 1.1.2 version: 1.1.2 - '@changesets/changelog-github': - specifier: ^0.5.0 - version: 0.5.0 '@changesets/cli': specifier: ~2.27.8 version: 2.27.8 + '@changesets/get-github-info': + specifier: ^0.6.0 + version: 0.6.0 '@eth-optimism/contracts': specifier: 0.6.0 version: 0.6.0(ethers@5.7.2) @@ -204,9 +204,6 @@ packages: '@changesets/changelog-git@0.2.0': resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} - '@changesets/changelog-github@0.5.0': - resolution: {integrity: sha512-zoeq2LJJVcPJcIotHRJEEA2qCqX0AQIeFE+L21L8sRLPVqDhSXY8ZWAt2sohtBpFZkBwu+LUwMSKRr2lMy3LJA==} - '@changesets/cli@2.27.8': resolution: {integrity: sha512-gZNyh+LdSsI82wBSHLQ3QN5J30P4uHKJ4fXgoGwQxfXwYFTJzDdvIJasZn8rYQtmKhyQuiBj4SSnLuKlxKWq4w==} hasBin: true @@ -1361,10 +1358,6 @@ packages: dot-case@2.1.1: resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==} - dotenv@8.6.0: - resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} - engines: {node: '>=10'} - elliptic@6.5.4: resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} @@ -3266,14 +3259,6 @@ snapshots: dependencies: '@changesets/types': 6.0.0 - '@changesets/changelog-github@0.5.0': - dependencies: - '@changesets/get-github-info': 0.6.0 - '@changesets/types': 6.0.0 - dotenv: 8.6.0 - transitivePeerDependencies: - - encoding - '@changesets/cli@2.27.8': dependencies: '@changesets/apply-release-plan': 7.0.5 @@ -4910,8 +4895,6 @@ snapshots: dependencies: no-case: 2.3.2 - dotenv@8.6.0: {} - elliptic@6.5.4: dependencies: bn.js: 4.12.0 diff --git a/contracts/src/v0.8/ccip/test/BaseTest.t.sol b/contracts/src/v0.8/ccip/test/BaseTest.t.sol index ea75b4eda99..2c54a49744f 100644 --- a/contracts/src/v0.8/ccip/test/BaseTest.t.sol +++ b/contracts/src/v0.8/ccip/test/BaseTest.t.sol @@ -74,7 +74,7 @@ contract BaseTest is Test { IRMNRemote internal s_mockRMNRemote; // nonce for pseudo-random number generation, not to be exposed to test suites - uint256 private randNonce; + uint256 private s_randNonce; function setUp() public virtual { // BaseTest.setUp is often called multiple times from tests' setUp due to inheritance. @@ -136,7 +136,7 @@ contract BaseTest is Test { /// @dev returns a pseudo-random bytes32 function _randomBytes32() internal returns (bytes32) { - return keccak256(abi.encodePacked(++randNonce)); + return keccak256(abi.encodePacked(++s_randNonce)); } /// @dev returns a pseudo-random number diff --git a/contracts/src/v0.8/ccip/test/NonceManager.t.sol b/contracts/src/v0.8/ccip/test/NonceManager.t.sol index 30b11df32e6..4c395e1dc54 100644 --- a/contracts/src/v0.8/ccip/test/NonceManager.t.sol +++ b/contracts/src/v0.8/ccip/test/NonceManager.t.sol @@ -4,17 +4,14 @@ pragma solidity 0.8.24; import {IEVM2AnyOnRamp} from "../interfaces/IEVM2AnyOnRamp.sol"; import {NonceManager} from "../NonceManager.sol"; -import {Router} from "../Router.sol"; import {Client} from "../libraries/Client.sol"; import {Internal} from "../libraries/Internal.sol"; -import {Pool} from "../libraries/Pool.sol"; -import {RateLimiter} from "../libraries/RateLimiter.sol"; import {OffRamp} from "../offRamp/OffRamp.sol"; import {OnRamp} from "../onRamp/OnRamp.sol"; import {BaseTest} from "./BaseTest.t.sol"; import {EVM2EVMOffRampHelper} from "./helpers/EVM2EVMOffRampHelper.sol"; import {OnRampHelper} from "./helpers/OnRampHelper.sol"; -import {OffRampSetup} from "./offRamp/OffRampSetup.t.sol"; +import {OffRampSetup} from "./offRamp/offRamp/OffRampSetup.t.sol"; import {OnRampSetup} from "./onRamp/OnRampSetup.t.sol"; import {Test} from "forge-std/Test.sol"; diff --git a/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol b/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol index c68907bb9f9..70cbc9c950b 100644 --- a/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/DefensiveExample.t.sol @@ -13,13 +13,13 @@ contract DefensiveExampleTest is OnRampSetup { event MessageRecovered(bytes32 indexed messageId); DefensiveExample internal s_receiver; - uint64 internal sourceChainSelector = 7331; + uint64 internal s_sourceChainSelector = 7331; function setUp() public virtual override { super.setUp(); s_receiver = new DefensiveExample(s_destRouter, IERC20(s_destFeeToken)); - s_receiver.enableChain(sourceChainSelector, abi.encode("")); + s_receiver.enableChain(s_sourceChainSelector, abi.encode("")); } function test_Recovery() public { @@ -44,7 +44,7 @@ contract DefensiveExampleTest is OnRampSetup { s_receiver.ccipReceive( Client.Any2EVMMessage({ messageId: messageId, - sourceChainSelector: sourceChainSelector, + sourceChainSelector: s_sourceChainSelector, sender: abi.encode(address(0)), // wrong sender, will revert internally data: "", destTokenAmounts: destTokenAmounts @@ -87,7 +87,7 @@ contract DefensiveExampleTest is OnRampSetup { s_receiver.ccipReceive( Client.Any2EVMMessage({ messageId: messageId, - sourceChainSelector: sourceChainSelector, + sourceChainSelector: s_sourceChainSelector, sender: abi.encode(address(s_receiver)), // correct sender data: "", destTokenAmounts: destTokenAmounts diff --git a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver.t.sol b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver.t.sol index 1791d784eed..6da86a06c7e 100644 --- a/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/EtherSenderReceiver.t.sol @@ -21,6 +21,7 @@ contract EtherSenderReceiverTest is Test { address internal constant OWNER = 0x00007e64E1fB0C487F25dd6D3601ff6aF8d32e4e; address internal constant ROUTER = 0x0F3779ee3a832D10158073ae2F5e61ac7FBBF880; address internal constant XCHAIN_RECEIVER = 0xBd91b2073218AF872BF73b65e2e5950ea356d147; + uint256 internal constant AMOUNT = 100; function setUp() public { vm.startPrank(OWNER); @@ -50,14 +51,12 @@ contract EtherSenderReceiverTest_constructor is EtherSenderReceiverTest { } contract EtherSenderReceiverTest_validateFeeToken is EtherSenderReceiverTest { - uint256 internal constant amount = 100; - error InsufficientMsgValue(uint256 gotAmount, uint256 msgValue); error TokenAmountNotEqualToMsgValue(uint256 gotAmount, uint256 msgValue); function test_validateFeeToken_valid_native() public { Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); - tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: amount}); + tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), data: "", @@ -66,12 +65,12 @@ contract EtherSenderReceiverTest_validateFeeToken is EtherSenderReceiverTest { extraArgs: "" }); - s_etherSenderReceiver.validateFeeToken{value: amount + 1}(message); + s_etherSenderReceiver.validateFeeToken{value: AMOUNT + 1}(message); } function test_validateFeeToken_valid_feeToken() public { Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); - tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: amount}); + tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), data: "", @@ -80,12 +79,12 @@ contract EtherSenderReceiverTest_validateFeeToken is EtherSenderReceiverTest { extraArgs: "" }); - s_etherSenderReceiver.validateFeeToken{value: amount}(message); + s_etherSenderReceiver.validateFeeToken{value: AMOUNT}(message); } function test_validateFeeToken_reverts_feeToken_tokenAmountNotEqualToMsgValue() public { Client.EVMTokenAmount[] memory tokenAmount = new Client.EVMTokenAmount[](1); - tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: amount}); + tokenAmount[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), data: "", @@ -94,8 +93,8 @@ contract EtherSenderReceiverTest_validateFeeToken is EtherSenderReceiverTest { extraArgs: "" }); - vm.expectRevert(abi.encodeWithSelector(TokenAmountNotEqualToMsgValue.selector, amount, amount + 1)); - s_etherSenderReceiver.validateFeeToken{value: amount + 1}(message); + vm.expectRevert(abi.encodeWithSelector(TokenAmountNotEqualToMsgValue.selector, AMOUNT, AMOUNT + 1)); + s_etherSenderReceiver.validateFeeToken{value: AMOUNT + 1}(message); } } @@ -105,15 +104,13 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { error InvalidWethAddress(address want, address got); error GasLimitTooLow(uint256 minLimit, uint256 gotLimit); - uint256 internal constant amount = 100; - function test_Fuzz_validatedMessage_msgSenderOverwrite( bytes memory data ) public view { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -127,7 +124,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, amount, "amount must be correct"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); } @@ -136,7 +133,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { address token ) public view { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({token: token, amount: amount}); + tokenAmounts[0] = Client.EVMTokenAmount({token: token, amount: AMOUNT}); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), data: "", @@ -149,7 +146,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, amount, "amount must be correct"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); } @@ -158,7 +155,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -172,7 +169,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, amount, "amount must be correct"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); } @@ -181,7 +178,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -195,7 +192,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, amount, "amount must be correct"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); } @@ -204,7 +201,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(42), // incorrect token. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -218,7 +215,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, amount, "amount must be correct"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); assertEq(validatedMessage.extraArgs, bytes(""), "extraArgs must be empty"); } @@ -227,7 +224,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -241,7 +238,7 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { assertEq(validatedMessage.receiver, abi.encode(XCHAIN_RECEIVER), "receiver must be XCHAIN_RECEIVER"); assertEq(validatedMessage.data, abi.encode(OWNER), "data must be msg.sender"); assertEq(validatedMessage.tokenAmounts[0].token, address(s_weth), "token must be weth"); - assertEq(validatedMessage.tokenAmounts[0].amount, amount, "amount must be correct"); + assertEq(validatedMessage.tokenAmounts[0].amount, AMOUNT, "amount must be correct"); assertEq(validatedMessage.feeToken, address(0), "feeToken must be 0"); assertEq( validatedMessage.extraArgs, @@ -252,8 +249,8 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { function test_validatedMessage_invalidTokenAmounts() public { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](2); - tokenAmounts[0] = Client.EVMTokenAmount({token: address(0), amount: amount}); - tokenAmounts[1] = Client.EVMTokenAmount({token: address(0), amount: amount}); + tokenAmounts[0] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); + tokenAmounts[1] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), data: "", @@ -268,13 +265,12 @@ contract EtherSenderReceiverTest_validatedMessage is EtherSenderReceiverTest { } contract EtherSenderReceiverTest_getFee is EtherSenderReceiverTest { - uint64 internal constant destinationChainSelector = 424242; - uint256 internal constant feeWei = 121212; - uint256 internal constant amount = 100; + uint64 internal constant DESTINATION_CHAIN_SELECTOR = 424242; + uint256 internal constant FEE_WEI = 121212; function test_getFee() public { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); - tokenAmounts[0] = Client.EVMTokenAmount({token: address(0), amount: amount}); + tokenAmounts[0] = Client.EVMTokenAmount({token: address(0), amount: AMOUNT}); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), data: "", @@ -287,18 +283,17 @@ contract EtherSenderReceiverTest_getFee is EtherSenderReceiverTest { vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), - abi.encode(feeWei) + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) ); - uint256 fee = s_etherSenderReceiver.getFee(destinationChainSelector, message); - assertEq(fee, feeWei, "fee must be feeWei"); + uint256 fee = s_etherSenderReceiver.getFee(DESTINATION_CHAIN_SELECTOR, message); + assertEq(fee, FEE_WEI, "fee must be feeWei"); } } contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTest { - uint256 internal constant amount = 100; - uint64 internal constant sourceChainSelector = 424242; + uint64 internal constant SOURCE_CHAIN_SELECTOR = 424242; address internal constant XCHAIN_SENDER = 0x9951529C13B01E542f7eE3b6D6665D292e9BA2E0; error InvalidTokenAmounts(uint256 gotAmounts); @@ -316,7 +311,7 @@ contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTest { destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: tokenAmount}); Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ messageId: keccak256(abi.encode("ccip send")), - sourceChainSelector: sourceChainSelector, + sourceChainSelector: SOURCE_CHAIN_SELECTOR, sender: abi.encode(XCHAIN_SENDER), data: abi.encode(OWNER), destTokenAmounts: destTokenAmounts @@ -333,7 +328,7 @@ contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTest { function test_ccipReceive_happyPath() public { Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: amount}); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ messageId: keccak256(abi.encode("ccip send")), sourceChainSelector: 424242, @@ -343,17 +338,17 @@ contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTest { }); // simulate a cross-chain token transfer, just transfer the weth to s_etherSenderReceiver. - s_weth.transfer(address(s_etherSenderReceiver), amount); + s_weth.transfer(address(s_etherSenderReceiver), AMOUNT); uint256 balanceBefore = OWNER.balance; s_etherSenderReceiver.publicCcipReceive(message); uint256 balanceAfter = OWNER.balance; - assertEq(balanceAfter, balanceBefore + amount, "balance must be correct"); + assertEq(balanceAfter, balanceBefore + AMOUNT, "balance must be correct"); } function test_ccipReceive_fallbackToWethTransfer() public { Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: amount}); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ messageId: keccak256(abi.encode("ccip send")), sourceChainSelector: 424242, @@ -363,20 +358,20 @@ contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTest { }); // simulate a cross-chain token transfer, just transfer the weth to s_etherSenderReceiver. - s_weth.transfer(address(s_etherSenderReceiver), amount); + s_weth.transfer(address(s_etherSenderReceiver), AMOUNT); uint256 balanceBefore = address(s_linkToken).balance; s_etherSenderReceiver.publicCcipReceive(message); uint256 balanceAfter = address(s_linkToken).balance; assertEq(balanceAfter, balanceBefore, "balance must be unchanged"); uint256 wethBalance = s_weth.balanceOf(address(s_linkToken)); - assertEq(wethBalance, amount, "weth balance must be correct"); + assertEq(wethBalance, AMOUNT, "weth balance must be correct"); } function test_ccipReceive_wrongTokenAmount() public { Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](2); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: amount}); - destTokenAmounts[1] = Client.EVMTokenAmount({token: address(s_weth), amount: amount}); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); + destTokenAmounts[1] = Client.EVMTokenAmount({token: address(s_weth), amount: AMOUNT}); Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ messageId: keccak256(abi.encode("ccip send")), sourceChainSelector: 424242, @@ -391,7 +386,7 @@ contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTest { function test_ccipReceive_wrongToken() public { Client.EVMTokenAmount[] memory destTokenAmounts = new Client.EVMTokenAmount[](1); - destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_someOtherWeth), amount: amount}); + destTokenAmounts[0] = Client.EVMTokenAmount({token: address(s_someOtherWeth), amount: AMOUNT}); Client.Any2EVMMessage memory message = Client.Any2EVMMessage({ messageId: keccak256(abi.encode("ccip send")), sourceChainSelector: 424242, @@ -408,19 +403,18 @@ contract EtherSenderReceiverTest_ccipReceive is EtherSenderReceiverTest { contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { error InsufficientFee(uint256 gotFee, uint256 fee); - uint256 internal constant amount = 100; - uint64 internal constant destinationChainSelector = 424242; - uint256 internal constant feeWei = 121212; - uint256 internal constant feeJuels = 232323; + uint64 internal constant DESTINATION_CHAIN_SELECTOR = 424242; + uint256 internal constant FEE_WEI = 121212; + uint256 internal constant FEE_JUELS = 232323; function test_Fuzz_ccipSend(uint256 feeFromRouter, uint256 feeSupplied) public { // cap the fuzzer because OWNER only has a million ether. - vm.assume(feeSupplied < 1_000_000 ether - amount); + vm.assume(feeSupplied < 1_000_000 ether - AMOUNT); Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -434,36 +428,36 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), abi.encode(feeFromRouter) ); if (feeSupplied < feeFromRouter) { vm.expectRevert(); - s_etherSenderReceiver.ccipSend{value: amount + feeSupplied}(destinationChainSelector, message); + s_etherSenderReceiver.ccipSend{value: AMOUNT + feeSupplied}(DESTINATION_CHAIN_SELECTOR, message); } else { bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); vm.mockCall( ROUTER, feeSupplied, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, destinationChainSelector, validatedMessage), + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), abi.encode(expectedMsgId) ); bytes32 actualMsgId = - s_etherSenderReceiver.ccipSend{value: amount + feeSupplied}(destinationChainSelector, message); + s_etherSenderReceiver.ccipSend{value: AMOUNT + feeSupplied}(DESTINATION_CHAIN_SELECTOR, message); assertEq(actualMsgId, expectedMsgId, "message id must be correct"); } } function test_Fuzz_ccipSend_feeToken(uint256 feeFromRouter, uint256 feeSupplied) public { // cap the fuzzer because OWNER only has a million LINK. - vm.assume(feeSupplied < 1_000_000 ether - amount); + vm.assume(feeSupplied < 1_000_000 ether - AMOUNT); Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -477,7 +471,7 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), abi.encode(feeFromRouter) ); @@ -485,16 +479,16 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { if (feeSupplied < feeFromRouter) { vm.expectRevert(); - s_etherSenderReceiver.ccipSend{value: amount}(destinationChainSelector, message); + s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); } else { bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, destinationChainSelector, validatedMessage), + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), abi.encode(expectedMsgId) ); - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: amount}(destinationChainSelector, message); + bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); assertEq(actualMsgId, expectedMsgId, "message id must be correct"); } } @@ -503,7 +497,7 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -517,21 +511,21 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), - abi.encode(feeWei) + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) ); - s_weth.approve(address(s_etherSenderReceiver), feeWei - 1); + s_weth.approve(address(s_etherSenderReceiver), FEE_WEI - 1); vm.expectRevert("SafeERC20: low-level call failed"); - s_etherSenderReceiver.ccipSend{value: amount}(destinationChainSelector, message); + s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); } function test_ccipSend_reverts_insufficientFee_feeToken() public { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -545,21 +539,21 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), - abi.encode(feeJuels) + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_JUELS) ); - s_linkToken.approve(address(s_etherSenderReceiver), feeJuels - 1); + s_linkToken.approve(address(s_etherSenderReceiver), FEE_JUELS - 1); vm.expectRevert("ERC20: insufficient allowance"); - s_etherSenderReceiver.ccipSend{value: amount}(destinationChainSelector, message); + s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); } function test_ccipSend_reverts_insufficientFee_native() public { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -573,19 +567,19 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), - abi.encode(feeWei) + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) ); vm.expectRevert(); - s_etherSenderReceiver.ccipSend{value: amount + feeWei - 1}(destinationChainSelector, message); + s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI - 1}(DESTINATION_CHAIN_SELECTOR, message); } function test_ccipSend_success_nativeExcess() public { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -600,20 +594,21 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), - abi.encode(feeWei) + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) ); // we assert that the correct value is sent to the router call, which should be // the msg.value - feeWei. vm.mockCall( ROUTER, - feeWei + 1, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, destinationChainSelector, validatedMessage), + FEE_WEI + 1, + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), abi.encode(expectedMsgId) ); - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: amount + feeWei + 1}(destinationChainSelector, message); + bytes32 actualMsgId = + s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI + 1}(DESTINATION_CHAIN_SELECTOR, message); assertEq(actualMsgId, expectedMsgId, "message id must be correct"); } @@ -621,7 +616,7 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -636,17 +631,17 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), - abi.encode(feeWei) + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) ); vm.mockCall( ROUTER, - feeWei, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, destinationChainSelector, validatedMessage), + FEE_WEI, + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), abi.encode(expectedMsgId) ); - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: amount + feeWei}(destinationChainSelector, message); + bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT + FEE_WEI}(DESTINATION_CHAIN_SELECTOR, message); assertEq(actualMsgId, expectedMsgId, "message id must be correct"); } @@ -654,7 +649,7 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -669,28 +664,28 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), - abi.encode(feeJuels) + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_JUELS) ); vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, destinationChainSelector, validatedMessage), + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), abi.encode(expectedMsgId) ); - s_linkToken.approve(address(s_etherSenderReceiver), feeJuels); + s_linkToken.approve(address(s_etherSenderReceiver), FEE_JUELS); - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: amount}(destinationChainSelector, message); + bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); assertEq(actualMsgId, expectedMsgId, "message id must be correct"); uint256 routerAllowance = s_linkToken.allowance(address(s_etherSenderReceiver), ROUTER); - assertEq(routerAllowance, feeJuels, "router allowance must be feeJuels"); + assertEq(routerAllowance, FEE_JUELS, "router allowance must be feeJuels"); } function test_ccipSend_success_weth() public { Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1); tokenAmounts[0] = Client.EVMTokenAmount({ token: address(0), // callers may not specify this. - amount: amount + amount: AMOUNT }); Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ receiver: abi.encode(XCHAIN_RECEIVER), @@ -705,18 +700,18 @@ contract EtherSenderReceiverTest_ccipSend is EtherSenderReceiverTest { bytes32 expectedMsgId = keccak256(abi.encode("ccip send")); vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.getFee.selector, destinationChainSelector, validatedMessage), - abi.encode(feeWei) + abi.encodeWithSelector(IRouterClient.getFee.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), + abi.encode(FEE_WEI) ); vm.mockCall( ROUTER, - abi.encodeWithSelector(IRouterClient.ccipSend.selector, destinationChainSelector, validatedMessage), + abi.encodeWithSelector(IRouterClient.ccipSend.selector, DESTINATION_CHAIN_SELECTOR, validatedMessage), abi.encode(expectedMsgId) ); - s_weth.approve(address(s_etherSenderReceiver), feeWei); + s_weth.approve(address(s_etherSenderReceiver), FEE_WEI); - bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: amount}(destinationChainSelector, message); + bytes32 actualMsgId = s_etherSenderReceiver.ccipSend{value: AMOUNT}(DESTINATION_CHAIN_SELECTOR, message); assertEq(actualMsgId, expectedMsgId, "message id must be correct"); uint256 routerAllowance = s_weth.allowance(address(s_etherSenderReceiver), ROUTER); assertEq(routerAllowance, type(uint256).max, "router allowance must be max for weth"); diff --git a/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol b/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol index d47ba1c54fb..308e2c087c4 100644 --- a/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol +++ b/contracts/src/v0.8/ccip/test/applications/PingPongDemo.t.sol @@ -3,9 +3,12 @@ pragma solidity 0.8.24; import {PingPongDemo} from "../../applications/PingPongDemo.sol"; import {Client} from "../../libraries/Client.sol"; -import "../onRamp/OnRampSetup.t.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {OnRamp} from "../../onRamp/OnRamp.sol"; +import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; + +import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; -// setup contract PingPongDappSetup is OnRampSetup { PingPongDemo internal s_pingPong; IERC20 internal s_feeToken; @@ -27,7 +30,7 @@ contract PingPongDappSetup is OnRampSetup { } contract PingPong_startPingPong is PingPongDappSetup { - uint256 internal pingPongNumber = 1; + uint256 internal s_pingPongNumber = 1; function test_StartPingPong_With_Sequenced_Ordered_Success() public { _assertPingPongSuccess(); @@ -41,7 +44,7 @@ contract PingPong_startPingPong is PingPongDappSetup { function _assertPingPongSuccess() internal { vm.expectEmit(); - emit PingPongDemo.Ping(pingPongNumber); + emit PingPongDemo.Ping(s_pingPongNumber); Internal.EVM2AnyRampMessage memory message; diff --git a/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol b/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol index 0c1cc714be9..3f262e2feb3 100644 --- a/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol +++ b/contracts/src/v0.8/ccip/test/attacks/onRamp/OnRampTokenPoolReentrancy.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.24; import {Client} from "../../../libraries/Client.sol"; -import {Internal} from "../../../libraries/Internal.sol"; import {OnRamp} from "../../../onRamp/OnRamp.sol"; import {TokenPool} from "../../../pools/TokenPool.sol"; import {OnRampSetup} from "../../onRamp/OnRampSetup.t.sol"; @@ -77,6 +76,7 @@ contract OnRampTokenPoolReentrancy is OnRampSetup { assertGt(expectedFee, 0); vm.expectRevert(OnRamp.ReentrancyGuardReentrantCall.selector); + // solhint-disable-next-line check-send-result s_facadeClient.send(amount); } } diff --git a/contracts/src/v0.8/ccip/test/capability/CCIPHome.t.sol b/contracts/src/v0.8/ccip/test/capability/CCIPHome.t.sol index d142e5fbfed..259506dd64a 100644 --- a/contracts/src/v0.8/ccip/test/capability/CCIPHome.t.sol +++ b/contracts/src/v0.8/ccip/test/capability/CCIPHome.t.sol @@ -8,7 +8,6 @@ import {CCIPHome} from "../../capability/CCIPHome.sol"; import {Internal} from "../../libraries/Internal.sol"; import {CCIPHomeHelper} from "../helpers/CCIPHomeHelper.sol"; import {Test} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; import {IERC165} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/interfaces/IERC165.sol"; diff --git a/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol b/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol index 83e567d965e..4a0c05933ca 100644 --- a/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol +++ b/contracts/src/v0.8/ccip/test/e2e/End2End.t.sol @@ -6,15 +6,23 @@ import {IRMNRemote} from "../../interfaces/IRMNRemote.sol"; import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; import {NonceManager} from "../../NonceManager.sol"; +import {Router} from "../../Router.sol"; +import {Client} from "../../libraries/Client.sol"; +import {Internal} from "../../libraries/Internal.sol"; +import {OffRamp} from "../../offRamp/OffRamp.sol"; +import {OnRamp} from "../../onRamp/OnRamp.sol"; import {LockReleaseTokenPool} from "../../pools/LockReleaseTokenPool.sol"; import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.sol"; -import "../helpers/MerkleHelper.sol"; -import "../offRamp/OffRampSetup.t.sol"; -import "../onRamp/OnRampSetup.t.sol"; +import {MerkleHelper} from "../helpers/MerkleHelper.sol"; +import {OnRampHelper} from "../helpers/OnRampHelper.sol"; +import {OffRampSetup} from "../offRamp/offRamp/OffRampSetup.t.sol"; +import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; + +import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; /// @notice This E2E test implements the following scenario: -/// 1. Send multiple messages from multiple source chains to a single destination chain (2 messages from source chain 1 and 1 from -/// source chain 2). +/// 1. Send multiple messages from multiple source chains to a single destination chain (2 messages from source chain +/// 1 and 1 from source chain 2). /// 2. Commit multiple merkle roots (1 for each source chain). /// 3. Batch execute all the committed messages. contract E2E is OnRampSetup, OffRampSetup { diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol index c622312a7ec..4cd34db04a3 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol @@ -2239,13 +2239,13 @@ contract FeeQuoter_KeystoneSetup is FeeQuoterSetup { address internal constant WORKFLOW_OWNER_1 = address(0x3); bytes10 internal constant WORKFLOW_NAME_1 = "workflow1"; bytes2 internal constant REPORT_NAME_1 = "01"; - address internal onReportTestToken1; - address internal onReportTestToken2; + address internal s_onReportTestToken1; + address internal s_onReportTestToken2; function setUp() public virtual override { super.setUp(); - onReportTestToken1 = s_sourceTokens[0]; - onReportTestToken2 = _deploySourceToken("onReportTestToken2", 0, 20); + s_onReportTestToken1 = s_sourceTokens[0]; + s_onReportTestToken2 = _deploySourceToken("onReportTestToken2", 0, 20); KeystoneFeedsPermissionHandler.Permission[] memory permissions = new KeystoneFeedsPermissionHandler.Permission[](1); permissions[0] = KeystoneFeedsPermissionHandler.Permission({ @@ -2257,11 +2257,11 @@ contract FeeQuoter_KeystoneSetup is FeeQuoterSetup { }); FeeQuoter.TokenPriceFeedUpdate[] memory tokenPriceFeeds = new FeeQuoter.TokenPriceFeedUpdate[](2); tokenPriceFeeds[0] = FeeQuoter.TokenPriceFeedUpdate({ - sourceToken: onReportTestToken1, + sourceToken: s_onReportTestToken1, feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 18, isEnabled: true}) }); tokenPriceFeeds[1] = FeeQuoter.TokenPriceFeedUpdate({ - sourceToken: onReportTestToken2, + sourceToken: s_onReportTestToken2, feedConfig: FeeQuoter.TokenPriceFeedConfig({dataFeedAddress: address(0x0), tokenDecimals: 20, isEnabled: true}) }); s_feeQuoter.setReportPermissions(permissions); @@ -2276,16 +2276,16 @@ contract FeeQuoter_onReport is FeeQuoter_KeystoneSetup { FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](2); report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); report[1] = - FeeQuoter.ReceivedCCIPFeedReport({token: onReportTestToken2, price: 4e18, timestamp: uint32(block.timestamp)}); + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken2, price: 4e18, timestamp: uint32(block.timestamp)}); uint224 expectedStoredToken1Price = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); uint224 expectedStoredToken2Price = s_feeQuoter.calculateRebasedValue(18, 20, report[1].price); vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(onReportTestToken1, expectedStoredToken1Price, block.timestamp); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredToken1Price, block.timestamp); vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(onReportTestToken2, expectedStoredToken2Price, block.timestamp); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken2, expectedStoredToken2Price, block.timestamp); changePrank(FORWARDER_1); s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); @@ -2304,20 +2304,23 @@ contract FeeQuoter_onReport is FeeQuoter_KeystoneSetup { FeeQuoter.ReceivedCCIPFeedReport[] memory report = new FeeQuoter.ReceivedCCIPFeedReport[](1); report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); + FeeQuoter.ReceivedCCIPFeedReport({token: s_onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp)}); uint224 expectedStoredTokenPrice = s_feeQuoter.calculateRebasedValue(18, 18, report[0].price); vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(onReportTestToken1, expectedStoredTokenPrice, block.timestamp); + emit FeeQuoter.UsdPerTokenUpdated(s_onReportTestToken1, expectedStoredTokenPrice, block.timestamp); changePrank(FORWARDER_1); //setting the correct price and time with the correct report s_feeQuoter.onReport(encodedPermissionsMetadata, abi.encode(report)); //create a stale report - report[0] = - FeeQuoter.ReceivedCCIPFeedReport({token: onReportTestToken1, price: 4e18, timestamp: uint32(block.timestamp - 1)}); + report[0] = FeeQuoter.ReceivedCCIPFeedReport({ + token: s_onReportTestToken1, + price: 4e18, + timestamp: uint32(block.timestamp - 1) + }); //record logs to check no events were emitted vm.recordLogs(); diff --git a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol index d8a9a06bb35..12f535f9985 100644 --- a/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoterSetup.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {IFeeQuoter} from "../../interfaces/IFeeQuoter.sol"; - import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; import {FeeQuoter} from "../../FeeQuoter.sol"; import {Client} from "../../libraries/Client.sol"; diff --git a/contracts/src/v0.8/ccip/test/mocks/test/MockRouterTest.t.sol b/contracts/src/v0.8/ccip/test/mocks/test/MockRouterTest.t.sol index 6cbe7bf58f4..cd0aabf1776 100644 --- a/contracts/src/v0.8/ccip/test/mocks/test/MockRouterTest.t.sol +++ b/contracts/src/v0.8/ccip/test/mocks/test/MockRouterTest.t.sol @@ -13,7 +13,7 @@ contract MockRouterTest is TokenSetup { MockCCIPRouter public mockRouter; - uint64 public constant mockChainSelector = 123456; + uint64 public constant MOCK_CHAIN_SELECTOR = 123456; Client.EVM2AnyMessage public message; @@ -34,26 +34,26 @@ contract MockRouterTest is TokenSetup { function test_ccipSendWithInsufficientNativeTokens_Revert() public { //Should revert because did not include sufficient eth to pay for fees vm.expectRevert(IRouterClient.InsufficientFeeTokenAmount.selector); - mockRouter.ccipSend(mockChainSelector, message); + mockRouter.ccipSend(MOCK_CHAIN_SELECTOR, message); } function test_ccipSendWithSufficientNativeFeeTokens_Success() public { //ccipSend with sufficient native tokens for fees - mockRouter.ccipSend{value: 0.1 ether}(mockChainSelector, message); + mockRouter.ccipSend{value: 0.1 ether}(MOCK_CHAIN_SELECTOR, message); } function test_ccipSendWithInvalidMsgValue_Revert() public { message.feeToken = address(1); //Set to non native-token fees vm.expectRevert(IRouterClient.InvalidMsgValue.selector); - mockRouter.ccipSend{value: 0.1 ether}(mockChainSelector, message); + mockRouter.ccipSend{value: 0.1 ether}(MOCK_CHAIN_SELECTOR, message); } function test_ccipSendWithLinkFeeTokenbutInsufficientAllowance_Revert() public { message.feeToken = s_sourceFeeToken; vm.expectRevert(bytes("ERC20: insufficient allowance")); - mockRouter.ccipSend(mockChainSelector, message); + mockRouter.ccipSend(MOCK_CHAIN_SELECTOR, message); } function test_ccipSendWithLinkFeeTokenAndValidMsgValue_Success() public { @@ -63,6 +63,6 @@ contract MockRouterTest is TokenSetup { IERC20(s_sourceFeeToken).safeApprove(address(mockRouter), type(uint256).max); - mockRouter.ccipSend(mockChainSelector, message); + mockRouter.ccipSend(MOCK_CHAIN_SELECTOR, message); } } diff --git a/contracts/src/v0.8/ccip/test/offRamp/OffRamp.t.sol b/contracts/src/v0.8/ccip/test/offRamp/OffRamp.t.sol deleted file mode 100644 index 1601c64d1b1..00000000000 --- a/contracts/src/v0.8/ccip/test/offRamp/OffRamp.t.sol +++ /dev/null @@ -1,3850 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.24; - -import {IFeeQuoter} from "../../interfaces/IFeeQuoter.sol"; -import {IMessageInterceptor} from "../../interfaces/IMessageInterceptor.sol"; -import {IRMNRemote} from "../../interfaces/IRMNRemote.sol"; -import {IRouter} from "../../interfaces/IRouter.sol"; -import {ITokenAdminRegistry} from "../../interfaces/ITokenAdminRegistry.sol"; - -import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {CallWithExactGas} from "../../../shared/call/CallWithExactGas.sol"; -import {FeeQuoter} from "../../FeeQuoter.sol"; -import {NonceManager} from "../../NonceManager.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {Pool} from "../../libraries/Pool.sol"; -import {RateLimiter} from "../../libraries/RateLimiter.sol"; -import {MultiOCR3Base} from "../../ocr/MultiOCR3Base.sol"; -import {OffRamp} from "../../offRamp/OffRamp.sol"; -import {LockReleaseTokenPool} from "../../pools/LockReleaseTokenPool.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {MaybeRevertingBurnMintTokenPool} from "../helpers/MaybeRevertingBurnMintTokenPool.sol"; -import {OffRampHelper} from "../helpers/OffRampHelper.sol"; -import {ConformingReceiver} from "../helpers/receivers/ConformingReceiver.sol"; -import {MaybeRevertMessageReceiver} from "../helpers/receivers/MaybeRevertMessageReceiver.sol"; -import {MaybeRevertMessageReceiverNo165} from "../helpers/receivers/MaybeRevertMessageReceiverNo165.sol"; -import {ReentrancyAbuserMultiRamp} from "../helpers/receivers/ReentrancyAbuserMultiRamp.sol"; -import {OffRampSetup} from "./OffRampSetup.t.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; - -contract OffRamp_constructor is OffRampSetup { - function test_Constructor_Success() public { - OffRamp.StaticConfig memory staticConfig = OffRamp.StaticConfig({ - chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: address(s_inboundNonceManager) - }); - OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](2); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - sourceChainConfigs[1] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1 + 1, - onRamp: ON_RAMP_ADDRESS_2, - isEnabled: true - }); - - OffRamp.SourceChainConfig memory expectedSourceChainConfig1 = OffRamp.SourceChainConfig({ - router: s_destRouter, - isEnabled: true, - minSeqNr: 1, - onRamp: sourceChainConfigs[0].onRamp - }); - - OffRamp.SourceChainConfig memory expectedSourceChainConfig2 = OffRamp.SourceChainConfig({ - router: s_destRouter, - isEnabled: true, - minSeqNr: 1, - onRamp: sourceChainConfigs[1].onRamp - }); - - uint64[] memory expectedSourceChainSelectors = new uint64[](2); - expectedSourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; - expectedSourceChainSelectors[1] = SOURCE_CHAIN_SELECTOR_1 + 1; - - vm.expectEmit(); - emit OffRamp.StaticConfigSet(staticConfig); - - vm.expectEmit(); - emit OffRamp.DynamicConfigSet(dynamicConfig); - - vm.expectEmit(); - emit OffRamp.SourceChainSelectorAdded(SOURCE_CHAIN_SELECTOR_1); - - vm.expectEmit(); - emit OffRamp.SourceChainConfigSet(SOURCE_CHAIN_SELECTOR_1, expectedSourceChainConfig1); - - vm.expectEmit(); - emit OffRamp.SourceChainSelectorAdded(SOURCE_CHAIN_SELECTOR_1 + 1); - - vm.expectEmit(); - emit OffRamp.SourceChainConfigSet(SOURCE_CHAIN_SELECTOR_1 + 1, expectedSourceChainConfig2); - - s_offRamp = new OffRampHelper(staticConfig, dynamicConfig, sourceChainConfigs); - - MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: uint8(Internal.OCRPluginType.Execution), - configDigest: s_configDigestExec, - F: s_F, - isSignatureVerificationEnabled: false, - signers: s_emptySigners, - transmitters: s_validTransmitters - }); - - s_offRamp.setOCR3Configs(ocrConfigs); - - // Static config - OffRamp.StaticConfig memory gotStaticConfig = s_offRamp.getStaticConfig(); - assertEq(staticConfig.chainSelector, gotStaticConfig.chainSelector); - assertEq(address(staticConfig.rmnRemote), address(gotStaticConfig.rmnRemote)); - assertEq(staticConfig.tokenAdminRegistry, gotStaticConfig.tokenAdminRegistry); - - // Dynamic config - OffRamp.DynamicConfig memory gotDynamicConfig = s_offRamp.getDynamicConfig(); - _assertSameConfig(dynamicConfig, gotDynamicConfig); - - // OCR Config - MultiOCR3Base.OCRConfig memory expectedOCRConfig = MultiOCR3Base.OCRConfig({ - configInfo: MultiOCR3Base.ConfigInfo({ - configDigest: ocrConfigs[0].configDigest, - F: ocrConfigs[0].F, - n: 0, - isSignatureVerificationEnabled: ocrConfigs[0].isSignatureVerificationEnabled - }), - signers: s_emptySigners, - transmitters: s_validTransmitters - }); - MultiOCR3Base.OCRConfig memory gotOCRConfig = s_offRamp.latestConfigDetails(uint8(Internal.OCRPluginType.Execution)); - _assertOCRConfigEquality(expectedOCRConfig, gotOCRConfig); - - (uint64[] memory actualSourceChainSelectors, OffRamp.SourceChainConfig[] memory actualSourceChainConfigs) = - s_offRamp.getAllSourceChainConfigs(); - - _assertSourceChainConfigEquality(actualSourceChainConfigs[0], expectedSourceChainConfig1); - _assertSourceChainConfigEquality(actualSourceChainConfigs[1], expectedSourceChainConfig2); - - // OffRamp initial values - assertEq("OffRamp 1.6.0-dev", s_offRamp.typeAndVersion()); - assertEq(OWNER, s_offRamp.owner()); - assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); - - // assertion for source chain selector - for (uint256 i = 0; i < expectedSourceChainSelectors.length; i++) { - assertEq(expectedSourceChainSelectors[i], actualSourceChainSelectors[i]); - } - } - - // Revert - function test_ZeroOnRampAddress_Revert() public { - uint64[] memory sourceChainSelectors = new uint64[](1); - sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: new bytes(0), - isEnabled: true - }); - - vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); - - s_offRamp = new OffRampHelper( - OffRamp.StaticConfig({ - chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: address(s_inboundNonceManager) - }), - _generateDynamicOffRampConfig(address(s_feeQuoter)), - sourceChainConfigs - ); - } - - function test_SourceChainSelector_Revert() public { - uint64[] memory sourceChainSelectors = new uint64[](1); - sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: 0, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - - vm.expectRevert(OffRamp.ZeroChainSelectorNotAllowed.selector); - - s_offRamp = new OffRampHelper( - OffRamp.StaticConfig({ - chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: address(s_inboundNonceManager) - }), - _generateDynamicOffRampConfig(address(s_feeQuoter)), - sourceChainConfigs - ); - } - - function test_ZeroRMNRemote_Revert() public { - uint64[] memory sourceChainSelectors = new uint64[](1); - sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); - - vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); - - s_offRamp = new OffRampHelper( - OffRamp.StaticConfig({ - chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: IRMNRemote(ZERO_ADDRESS), - tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: address(s_inboundNonceManager) - }), - _generateDynamicOffRampConfig(address(s_feeQuoter)), - sourceChainConfigs - ); - } - - function test_ZeroChainSelector_Revert() public { - uint64[] memory sourceChainSelectors = new uint64[](1); - sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); - - vm.expectRevert(OffRamp.ZeroChainSelectorNotAllowed.selector); - - s_offRamp = new OffRampHelper( - OffRamp.StaticConfig({ - chainSelector: 0, - rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: address(s_inboundNonceManager) - }), - _generateDynamicOffRampConfig(address(s_feeQuoter)), - sourceChainConfigs - ); - } - - function test_ZeroTokenAdminRegistry_Revert() public { - uint64[] memory sourceChainSelectors = new uint64[](1); - sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); - - vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); - - s_offRamp = new OffRampHelper( - OffRamp.StaticConfig({ - chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: ZERO_ADDRESS, - nonceManager: address(s_inboundNonceManager) - }), - _generateDynamicOffRampConfig(address(s_feeQuoter)), - sourceChainConfigs - ); - } - - function test_ZeroNonceManager_Revert() public { - uint64[] memory sourceChainSelectors = new uint64[](1); - sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); - - vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); - - s_offRamp = new OffRampHelper( - OffRamp.StaticConfig({ - chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: ZERO_ADDRESS - }), - _generateDynamicOffRampConfig(address(s_feeQuoter)), - sourceChainConfigs - ); - } -} - -contract OffRamp_setDynamicConfig is OffRampSetup { - function test_SetDynamicConfig_Success() public { - OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); - - vm.expectEmit(); - emit OffRamp.DynamicConfigSet(dynamicConfig); - - s_offRamp.setDynamicConfig(dynamicConfig); - - OffRamp.DynamicConfig memory newConfig = s_offRamp.getDynamicConfig(); - _assertSameConfig(dynamicConfig, newConfig); - } - - function test_SetDynamicConfigWithInterceptor_Success() public { - OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); - dynamicConfig.messageInterceptor = address(s_inboundMessageInterceptor); - - vm.expectEmit(); - emit OffRamp.DynamicConfigSet(dynamicConfig); - - s_offRamp.setDynamicConfig(dynamicConfig); - - OffRamp.DynamicConfig memory newConfig = s_offRamp.getDynamicConfig(); - _assertSameConfig(dynamicConfig, newConfig); - } - - // Reverts - - function test_NonOwner_Revert() public { - vm.startPrank(STRANGER); - OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); - - vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); - - s_offRamp.setDynamicConfig(dynamicConfig); - } - - function test_FeeQuoterZeroAddress_Revert() public { - OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(ZERO_ADDRESS); - - vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); - - s_offRamp.setDynamicConfig(dynamicConfig); - } -} - -contract OffRamp_ccipReceive is OffRampSetup { - // Reverts - - function test_Reverts() public { - Client.Any2EVMMessage memory message = - _convertToGeneralMessage(_generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1)); - vm.expectRevert(); - s_offRamp.ccipReceive(message); - } -} - -contract OffRamp_executeSingleReport is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_3, 1); - } - - function test_SingleMessageNoTokens_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - messages[0].header.nonce++; - messages[0].header.sequenceNumber++; - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - assertGt(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender), nonceBefore); - } - - function test_SingleMessageNoTokensUnordered_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - messages[0].header.nonce = 0; - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - // Nonce never increments on unordered messages. - uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertEq( - s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender), - nonceBefore, - "nonce must remain unchanged on unordered messages" - ); - - messages[0].header.sequenceNumber++; - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - // Nonce never increments on unordered messages. - nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - assertEq( - s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender), - nonceBefore, - "nonce must remain unchanged on unordered messages" - ); - } - - function test_SingleMessageNoTokensOtherChain_Success() public { - Internal.Any2EVMRampMessage[] memory messagesChain1 = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesChain1), new OffRamp.GasLimitOverride[](0) - ); - - uint64 nonceChain1 = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messagesChain1[0].sender); - assertGt(nonceChain1, 0); - - Internal.Any2EVMRampMessage[] memory messagesChain2 = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3); - assertEq(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, messagesChain2[0].sender), 0); - - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messagesChain2), new OffRamp.GasLimitOverride[](0) - ); - assertGt(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, messagesChain2[0].sender), 0); - - // Other chain's nonce is unaffected - assertEq(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messagesChain1[0].sender), nonceChain1); - } - - function test_ReceiverError_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - bytes memory realError1 = new bytes(2); - realError1[0] = 0xbe; - realError1[1] = 0xef; - s_reverting_receiver.setErr(realError1); - - messages[0].receiver = address(s_reverting_receiver); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - // Nonce should increment on non-strict - assertEq(uint64(0), s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER))); - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector( - OffRamp.ReceiverError.selector, - abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, realError1) - ) - ); - assertEq(uint64(1), s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER))); - } - - function test_SkippedIncorrectNonce_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - messages[0].header.nonce++; - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - vm.expectEmit(); - emit NonceManager.SkippedIncorrectNonce( - messages[0].header.sourceChainSelector, messages[0].header.nonce, messages[0].sender - ); - - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - } - - function test_SkippedIncorrectNonceStillExecutes_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - messages[1].header.nonce++; - messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); - - vm.expectEmit(); - emit NonceManager.SkippedIncorrectNonce(SOURCE_CHAIN_SELECTOR_1, messages[1].header.nonce, messages[1].sender); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test__execute_SkippedAlreadyExecutedMessage_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - vm.expectEmit(); - emit OffRamp.SkippedAlreadyExecutedMessage(SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber); - - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - } - - function test__execute_SkippedAlreadyExecutedMessageUnordered_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - messages[0].header.nonce = 0; - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - vm.expectEmit(); - emit OffRamp.SkippedAlreadyExecutedMessage(SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber); - - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - } - - // Send a message to a contract that does not implement the CCIPReceiver interface - // This should execute successfully. - function test_SingleMessageToNonCCIPReceiver_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - MaybeRevertMessageReceiverNo165 newReceiver = new MaybeRevertMessageReceiverNo165(true); - messages[0].receiver = address(newReceiver); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_SingleMessagesNoTokensSuccess_gas() public { - vm.pauseGasMetering(); - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - vm.resumeGasMetering(); - vm.recordLogs(); - s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_TwoMessagesWithTokensSuccess_gas() public { - vm.pauseGasMetering(); - Internal.Any2EVMRampMessage[] memory messages = - _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - // Set message 1 to use another receiver to simulate more fair gas costs - messages[1].receiver = address(s_secondary_receiver); - messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); - - Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - vm.resumeGasMetering(); - vm.recordLogs(); - s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages[1].header.sequenceNumber, - messages[1].header.messageId, - _hashMessage(messages[1], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_TwoMessagesWithTokensAndGE_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - // Set message 1 to use another receiver to simulate more fair gas costs - messages[1].receiver = address(s_secondary_receiver); - messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); - - assertEq(uint64(0), s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER))); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), _getGasLimitsFromMessages(messages) - ); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages[1].header.sequenceNumber, - messages[1].header.messageId, - _hashMessage(messages[1], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - assertEq(uint64(2), s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER))); - } - - function test_Fuzz_InterleavingOrderedAndUnorderedMessages_Success( - bool[7] memory orderings - ) public { - Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](orderings.length); - // number of tokens needs to be capped otherwise we hit UnsupportedNumberOfTokens. - Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](3); - for (uint256 i = 0; i < 3; ++i) { - tokenAmounts[i].token = s_sourceTokens[i % s_sourceTokens.length]; - tokenAmounts[i].amount = 1e18; - } - uint64 expectedNonce = 0; - - for (uint256 i = 0; i < orderings.length; ++i) { - messages[i] = - _generateAny2EVMMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, uint64(i + 1), tokenAmounts, !orderings[i]); - if (orderings[i]) { - messages[i].header.nonce = ++expectedNonce; - } - messages[i].header.messageId = _hashMessage(messages[i], ON_RAMP_ADDRESS_1); - } - - uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER)); - assertEq(uint64(0), nonceBefore, "nonce before exec should be 0"); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), _getGasLimitsFromMessages(messages) - ); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - // all executions should succeed. - for (uint256 i = 0; i < orderings.length; ++i) { - assertEq( - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, messages[i].header.sequenceNumber)), - uint256(Internal.MessageExecutionState.SUCCESS) - ); - - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages[i].header.sequenceNumber, - messages[i].header.messageId, - _hashMessage(messages[i], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - assertEq( - nonceBefore + expectedNonce, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER)) - ); - } - - function test_InvalidSourcePoolAddress_Success() public { - address fakePoolAddress = address(0x0000000000333333); - - Internal.Any2EVMRampMessage[] memory messages = - _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - messages[0].tokenAmounts[0].sourcePoolAddress = abi.encode(fakePoolAddress); - - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector( - OffRamp.TokenHandlingError.selector, - abi.encodeWithSelector(TokenPool.InvalidSourcePoolAddress.selector, abi.encode(fakePoolAddress)) - ) - ); - } - - function test_WithCurseOnAnotherSourceChain_Success() public { - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_2, true); - s_offRamp.executeSingleReport( - _generateReportFromMessages( - SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) - ), - new OffRamp.GasLimitOverride[](0) - ); - } - - function test_Unhealthy_Success() public { - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); - - vm.expectEmit(); - emit OffRamp.SkippedReportExecution(SOURCE_CHAIN_SELECTOR_1); - s_offRamp.executeSingleReport( - _generateReportFromMessages( - SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) - ), - new OffRamp.GasLimitOverride[](0) - ); - - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, false); - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages( - SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) - ), - new OffRamp.GasLimitOverride[](0) - ); - - _assertNoEmit(OffRamp.SkippedReportExecution.selector); - } - - // Reverts - - function test_MismatchingDestChainSelector_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3); - messages[0].header.destChainSelector = DEST_CHAIN_SELECTOR + 1; - - Internal.ExecutionReport memory executionReport = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - vm.expectRevert( - abi.encodeWithSelector(OffRamp.InvalidMessageDestChainSelector.selector, messages[0].header.destChainSelector) - ); - s_offRamp.executeSingleReport(executionReport, new OffRamp.GasLimitOverride[](0)); - } - - function test_UnhealthySingleChainCurse_Revert() public { - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); - vm.expectEmit(); - emit OffRamp.SkippedReportExecution(SOURCE_CHAIN_SELECTOR_1); - s_offRamp.executeSingleReport( - _generateReportFromMessages( - SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) - ), - new OffRamp.GasLimitOverride[](0) - ); - vm.recordLogs(); - // Uncurse should succeed - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, false); - s_offRamp.executeSingleReport( - _generateReportFromMessages( - SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) - ), - new OffRamp.GasLimitOverride[](0) - ); - _assertNoEmit(OffRamp.SkippedReportExecution.selector); - } - - function test_UnexpectedTokenData_Revert() public { - Internal.ExecutionReport memory report = _generateReportFromMessages( - SOURCE_CHAIN_SELECTOR_1, _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) - ); - report.offchainTokenData = new bytes[][](report.messages.length + 1); - - vm.expectRevert(OffRamp.UnexpectedTokenData.selector); - - s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); - } - - function test_EmptyReport_Revert() public { - vm.expectRevert(abi.encodeWithSelector(OffRamp.EmptyReport.selector, SOURCE_CHAIN_SELECTOR_1)); - - s_offRamp.executeSingleReport( - Internal.ExecutionReport({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - proofs: new bytes32[](0), - proofFlagBits: 0, - messages: new Internal.Any2EVMRampMessage[](0), - offchainTokenData: new bytes[][](0) - }), - new OffRamp.GasLimitOverride[](0) - ); - } - - function test_RootNotCommitted_Revert() public { - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 0); - vm.expectRevert(abi.encodeWithSelector(OffRamp.RootNotCommitted.selector, SOURCE_CHAIN_SELECTOR_1)); - - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), _getGasLimitsFromMessages(messages) - ); - } - - function test_ManualExecutionNotYetEnabled_Revert() public { - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, BLOCK_TIME); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.ManualExecutionNotYetEnabled.selector, SOURCE_CHAIN_SELECTOR_1)); - - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), _getGasLimitsFromMessages(messages) - ); - } - - function test_NonExistingSourceChain_Revert() public { - uint64 newSourceChainSelector = SOURCE_CHAIN_SELECTOR_1 + 1; - bytes memory newOnRamp = abi.encode(ON_RAMP_ADDRESS, 1); - - Internal.Any2EVMRampMessage[] memory messages = _generateSingleBasicMessage(newSourceChainSelector, newOnRamp); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.SourceChainNotEnabled.selector, newSourceChainSelector)); - s_offRamp.executeSingleReport( - _generateReportFromMessages(newSourceChainSelector, messages), new OffRamp.GasLimitOverride[](0) - ); - } - - function test_DisabledSourceChain_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_2, ON_RAMP_ADDRESS_2); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.SourceChainNotEnabled.selector, SOURCE_CHAIN_SELECTOR_2)); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_2, messages), new OffRamp.GasLimitOverride[](0) - ); - } - - function test_TokenDataMismatch_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - report.offchainTokenData[0] = new bytes[](messages[0].tokenAmounts.length + 1); - - vm.expectRevert( - abi.encodeWithSelector( - OffRamp.TokenDataMismatch.selector, SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber - ) - ); - s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); - } - - function test_RouterYULCall_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - // gas limit too high, Router's external call should revert - messages[0].gasLimit = 1e36; - messages[0].receiver = address(new ConformingReceiver(address(s_destRouter), s_destFeeToken)); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - Internal.ExecutionReport memory executionReport = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - vm.recordLogs(); - s_offRamp.executeSingleReport(executionReport, new OffRamp.GasLimitOverride[](0)); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector(CallWithExactGas.NotEnoughGasForCall.selector) - ); - } - - function test_RetryFailedMessageWithoutManualExecution_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - bytes memory realError1 = new bytes(2); - realError1[0] = 0xbe; - realError1[1] = 0xef; - s_reverting_receiver.setErr(realError1); - - messages[0].receiver = address(s_reverting_receiver); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector( - OffRamp.ReceiverError.selector, - abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, realError1) - ) - ); - - // The second time should skip the msg - vm.expectEmit(); - emit OffRamp.AlreadyAttempted(SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber); - - s_offRamp.executeSingleReport( - _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) - ); - } - - function _constructCommitReport( - bytes32 merkleRoot - ) internal view returns (OffRamp.CommitReport memory) { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: abi.encode(ON_RAMP_ADDRESS_1), - minSeqNr: 1, - maxSeqNr: 2, - merkleRoot: merkleRoot - }); - - return OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }); - } -} - -contract OffRamp_executeSingleMessage is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - vm.startPrank(address(s_offRamp)); - } - - function test_executeSingleMessage_NoTokens_Success() public { - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - - Client.Any2EVMMessage memory expectedAny2EvmMessage = Client.Any2EVMMessage({ - messageId: message.header.messageId, - sourceChainSelector: message.header.sourceChainSelector, - sender: message.sender, - data: message.data, - destTokenAmounts: new Client.EVMTokenAmount[](0) - }); - vm.expectCall( - address(s_destRouter), - abi.encodeWithSelector( - IRouter.routeMessage.selector, - expectedAny2EvmMessage, - Internal.GAS_FOR_CALL_EXACT_CHECK, - message.gasLimit, - message.receiver - ) - ); - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } - - function test_executeSingleMessage_WithTokens_Success() public { - Internal.Any2EVMRampMessage memory message = - _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1)[0]; - bytes[] memory offchainTokenData = new bytes[](message.tokenAmounts.length); - - vm.expectCall( - s_destPoolByToken[s_destTokens[0]], - abi.encodeWithSelector( - LockReleaseTokenPool.releaseOrMint.selector, - Pool.ReleaseOrMintInV1({ - originalSender: message.sender, - receiver: message.receiver, - amount: message.tokenAmounts[0].amount, - localToken: message.tokenAmounts[0].destTokenAddress, - remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, - sourcePoolAddress: message.tokenAmounts[0].sourcePoolAddress, - sourcePoolData: message.tokenAmounts[0].extraData, - offchainTokenData: offchainTokenData[0] - }) - ) - ); - - s_offRamp.executeSingleMessage(message, offchainTokenData, new uint32[](0)); - } - - function test_executeSingleMessage_WithVInterception_Success() public { - vm.stopPrank(); - vm.startPrank(OWNER); - _enableInboundMessageInterceptor(); - vm.startPrank(address(s_offRamp)); - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } - - function test_NonContract_Success() public { - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - message.receiver = STRANGER; - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } - - function test_NonContractWithTokens_Success() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1000; - amounts[1] = 50; - vm.expectEmit(); - emit TokenPool.Released(address(s_offRamp), STRANGER, amounts[0]); - vm.expectEmit(); - emit TokenPool.Minted(address(s_offRamp), STRANGER, amounts[1]); - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); - message.receiver = STRANGER; - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } - - // Reverts - - function test_TokenHandlingError_Revert() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1000; - amounts[1] = 50; - - bytes memory errorMessage = "Random token pool issue"; - - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); - s_maybeRevertingPool.setShouldRevert(errorMessage); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, errorMessage)); - - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } - - function test_ZeroGasDONExecution_Revert() public { - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - message.gasLimit = 0; - - vm.expectRevert(abi.encodeWithSelector(OffRamp.ReceiverError.selector, "")); - - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } - - function test_MessageSender_Revert() public { - vm.stopPrank(); - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - vm.expectRevert(OffRamp.CanOnlySelfCall.selector); - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } - - function test_executeSingleMessage_WithFailingValidation_Revert() public { - vm.stopPrank(); - vm.startPrank(OWNER); - _enableInboundMessageInterceptor(); - vm.startPrank(address(s_offRamp)); - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - s_inboundMessageInterceptor.setMessageIdValidationState(message.header.messageId, true); - vm.expectRevert( - abi.encodeWithSelector( - IMessageInterceptor.MessageValidationError.selector, - abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) - ) - ); - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } - - function test_executeSingleMessage_WithFailingValidationNoRouterCall_Revert() public { - vm.stopPrank(); - vm.startPrank(OWNER); - _enableInboundMessageInterceptor(); - vm.startPrank(address(s_offRamp)); - - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - - // Setup the receiver to a non-CCIP Receiver, which will skip the Router call (but should still perform the validation) - MaybeRevertMessageReceiverNo165 newReceiver = new MaybeRevertMessageReceiverNo165(true); - message.receiver = address(newReceiver); - message.header.messageId = _hashMessage(message, ON_RAMP_ADDRESS_1); - - s_inboundMessageInterceptor.setMessageIdValidationState(message.header.messageId, true); - vm.expectRevert( - abi.encodeWithSelector( - IMessageInterceptor.MessageValidationError.selector, - abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) - ) - ); - s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - } -} - -contract OffRamp_batchExecute is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_3, 1); - } - - function test_SingleReport_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); - - vm.recordLogs(); - s_offRamp.batchExecute( - _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) - ); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertGt(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender), nonceBefore); - } - - function test_MultipleReportsSameChain_Success() public { - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); - - messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); - messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages2); - - uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages1[0].sender); - vm.recordLogs(); - s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - assertExecutionStateChangedEventLogs( - logs, - messages1[0].header.sourceChainSelector, - messages1[0].header.sequenceNumber, - messages1[0].header.messageId, - _hashMessage(messages1[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - messages1[1].header.sourceChainSelector, - messages1[1].header.sequenceNumber, - messages1[1].header.messageId, - _hashMessage(messages1[1], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - messages2[0].header.sourceChainSelector, - messages2[0].header.sequenceNumber, - messages2[0].header.messageId, - _hashMessage(messages2[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertGt(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages1[0].sender), nonceBefore); - } - - function test_MultipleReportsDifferentChains_Success() public { - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); - - messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); - messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, 1); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); - - vm.recordLogs(); - - s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - assertExecutionStateChangedEventLogs( - logs, - messages1[0].header.sourceChainSelector, - messages1[0].header.sequenceNumber, - messages1[0].header.messageId, - _hashMessage(messages1[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - messages1[1].header.sourceChainSelector, - messages1[1].header.sequenceNumber, - messages1[1].header.messageId, - _hashMessage(messages1[1], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - messages2[0].header.sourceChainSelector, - messages2[0].header.sequenceNumber, - messages2[0].header.messageId, - _hashMessage(messages2[0], ON_RAMP_ADDRESS_3), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - uint64 nonceChain1 = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages1[0].sender); - uint64 nonceChain3 = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, messages2[0].sender); - - assertTrue(nonceChain1 != nonceChain3); - assertGt(nonceChain1, 0); - assertGt(nonceChain3, 0); - } - - function test_MultipleReportsDifferentChainsSkipCursedChain_Success() public { - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); - - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); - - messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); - messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, 1); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); - - vm.recordLogs(); - - vm.expectEmit(); - emit OffRamp.SkippedReportExecution(SOURCE_CHAIN_SELECTOR_1); - - s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - for (uint256 i = 0; i < logs.length; ++i) { - if (logs[i].topics[0] == OffRamp.ExecutionStateChanged.selector) { - uint64 logSourceChainSelector = uint64(uint256(logs[i].topics[1])); - uint64 logSequenceNumber = uint64(uint256(logs[i].topics[2])); - bytes32 logMessageId = bytes32(logs[i].topics[3]); - (bytes32 logMessageHash, uint8 logState,,) = abi.decode(logs[i].data, (bytes32, uint8, bytes, uint256)); - assertEq(logMessageId, messages2[0].header.messageId); - assertEq(logSourceChainSelector, messages2[0].header.sourceChainSelector); - assertEq(logSequenceNumber, messages2[0].header.sequenceNumber); - assertEq(logMessageId, messages2[0].header.messageId); - assertEq(logMessageHash, _hashMessage(messages2[0], ON_RAMP_ADDRESS_3)); - assertEq(logState, uint8(Internal.MessageExecutionState.SUCCESS)); - } - } - } - - function test_MultipleReportsSkipDuplicate_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - vm.expectEmit(); - emit OffRamp.SkippedAlreadyExecutedMessage(SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber); - - vm.recordLogs(); - s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); - assertExecutionStateChangedEventLogs( - messages[0].header.sourceChainSelector, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_Unhealthy_Success() public { - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); - vm.expectEmit(); - emit OffRamp.SkippedReportExecution(SOURCE_CHAIN_SELECTOR_1); - s_offRamp.batchExecute( - _generateBatchReportFromMessages( - SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) - ), - new OffRamp.GasLimitOverride[][](1) - ); - - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, false); - - vm.recordLogs(); - s_offRamp.batchExecute( - _generateBatchReportFromMessages( - SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) - ), - new OffRamp.GasLimitOverride[][](1) - ); - - _assertNoEmit(OffRamp.SkippedReportExecution.selector); - } - - // Reverts - function test_ZeroReports_Revert() public { - vm.expectRevert(OffRamp.EmptyBatch.selector); - s_offRamp.batchExecute(new Internal.ExecutionReport[](0), new OffRamp.GasLimitOverride[][](1)); - } - - function test_OutOfBoundsGasLimitsAccess_Revert() public { - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); - - messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); - messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages2); - - vm.expectRevert(); - s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](1)); - } -} - -contract OffRamp_manuallyExecute is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_3, 1); - } - - function test_manuallyExecute_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - messages[0].receiver = address(s_reverting_receiver); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - s_offRamp.batchExecute( - _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) - ); - - s_reverting_receiver.setRevert(false); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](messages.length); - - vm.recordLogs(); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_manuallyExecute_WithGasOverride_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - messages[0].receiver = address(s_reverting_receiver); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - s_offRamp.batchExecute( - _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) - ); - - s_reverting_receiver.setRevert(false); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); - gasLimitOverrides[0][0].receiverExecutionGasLimit += 1; - vm.recordLogs(); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_manuallyExecute_DoesNotRevertIfUntouched_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - messages[0].receiver = address(s_reverting_receiver); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - assertEq( - messages[0].header.nonce - 1, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender) - ); - - s_reverting_receiver.setRevert(true); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); - - vm.recordLogs(); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector( - OffRamp.ReceiverError.selector, abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, "") - ) - ); - - assertEq( - messages[0].header.nonce, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender) - ); - } - - function test_manuallyExecute_WithMultiReportGasOverride_Success() public { - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](3); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](2); - - for (uint64 i = 0; i < 3; ++i) { - messages1[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, i + 1); - messages1[i].receiver = address(s_reverting_receiver); - messages1[i].header.messageId = _hashMessage(messages1[i], ON_RAMP_ADDRESS_1); - } - - for (uint64 i = 0; i < 2; ++i) { - messages2[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, i + 1); - messages2[i].receiver = address(s_reverting_receiver); - messages2[i].header.messageId = _hashMessage(messages2[i], ON_RAMP_ADDRESS_3); - } - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); - - s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); - - s_reverting_receiver.setRevert(false); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](2); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages1); - gasLimitOverrides[1] = _getGasLimitsFromMessages(messages2); - - for (uint256 i = 0; i < 3; ++i) { - gasLimitOverrides[0][i].receiverExecutionGasLimit += 1; - } - - for (uint256 i = 0; i < 2; ++i) { - gasLimitOverrides[1][i].receiverExecutionGasLimit += 1; - } - - vm.recordLogs(); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - for (uint256 j = 0; j < 3; ++j) { - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages1[j].header.sequenceNumber, - messages1[j].header.messageId, - _hashMessage(messages1[j], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - for (uint256 k = 0; k < 2; ++k) { - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_3, - messages2[k].header.sequenceNumber, - messages2[k].header.messageId, - _hashMessage(messages2[k], ON_RAMP_ADDRESS_3), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - } - - function test_manuallyExecute_WithPartialMessages_Success() public { - Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](3); - - for (uint64 i = 0; i < 3; ++i) { - messages[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, i + 1); - } - - messages[1].receiver = address(s_reverting_receiver); - messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.batchExecute( - _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) - ); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages[1].header.sequenceNumber, - messages[1].header.messageId, - _hashMessage(messages[1], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector( - OffRamp.ReceiverError.selector, - abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, bytes("")) - ) - ); - - assertExecutionStateChangedEventLogs( - logs, - SOURCE_CHAIN_SELECTOR_1, - messages[2].header.sequenceNumber, - messages[2].header.messageId, - _hashMessage(messages[2], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - s_reverting_receiver.setRevert(false); - - // Only the 2nd message reverted - Internal.Any2EVMRampMessage[] memory newMessages = new Internal.Any2EVMRampMessage[](1); - newMessages[0] = messages[1]; - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(newMessages); - gasLimitOverrides[0][0].receiverExecutionGasLimit += 1; - - vm.recordLogs(); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, newMessages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_manuallyExecute_LowGasLimit_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - messages[0].gasLimit = 1; - messages[0].receiver = address(new ConformingReceiver(address(s_destRouter), s_destFeeToken)); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - vm.recordLogs(); - s_offRamp.batchExecute( - _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) - ); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector(OffRamp.ReceiverError.selector, "") - ); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](1); - gasLimitOverrides[0][0].receiverExecutionGasLimit = 100_000; - - vm.expectEmit(); - emit ConformingReceiver.MessageReceived(); - - vm.recordLogs(); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - // Reverts - - function test_manuallyExecute_ForkedChain_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - uint256 chain1 = block.chainid; - uint256 chain2 = chain1 + 1; - vm.chainId(chain2); - vm.expectRevert(abi.encodeWithSelector(MultiOCR3Base.ForkedChain.selector, chain1, chain2)); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); - - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - } - - function test_ManualExecGasLimitMismatchSingleReport_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](2); - messages[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); - - Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - // No overrides for report - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, new OffRamp.GasLimitOverride[][](0)); - - // No messages - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - - // 1 message missing - gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](1); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - - // 1 message in excess - gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](3); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - } - - function test_manuallyExecute_GasLimitMismatchMultipleReports_Revert() public { - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); - - messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); - messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, 1); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, new OffRamp.GasLimitOverride[][](0)); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, new OffRamp.GasLimitOverride[][](1)); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](2); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - - // 2nd report empty - gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](2); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - - // 1st report empty - gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](0); - gasLimitOverrides[1] = new OffRamp.GasLimitOverride[](1); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - - // 1st report oversized - gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](3); - - vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - } - - function test_manuallyExecute_InvalidReceiverExecutionGasLimit_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); - gasLimitOverrides[0][0].receiverExecutionGasLimit--; - - vm.expectRevert( - abi.encodeWithSelector( - OffRamp.InvalidManualExecutionGasLimit.selector, - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.messageId, - gasLimitOverrides[0][0].receiverExecutionGasLimit - ) - ); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - } - - function test_manuallyExecute_DestinationGasAmountCountMismatch_Revert() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1000; - amounts[1] = 1000; - Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](1); - messages[0] = _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); - // empty tokenGasOverride array provided - vm.expectRevert( - abi.encodeWithSelector(OffRamp.ManualExecutionGasAmountCountMismatch.selector, messages[0].header.messageId, 1) - ); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - - //trying with excesss elements tokenGasOverride array provided - gasLimitOverrides[0][0].tokenGasOverrides = new uint32[](3); - vm.expectRevert( - abi.encodeWithSelector(OffRamp.ManualExecutionGasAmountCountMismatch.selector, messages[0].header.messageId, 1) - ); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - } - - function test_manuallyExecute_InvalidTokenGasOverride_Revert() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1000; - amounts[1] = 1000; - Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](1); - messages[0] = _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); - uint32[] memory tokenGasOverrides = new uint32[](2); - tokenGasOverrides[0] = DEFAULT_TOKEN_DEST_GAS_OVERHEAD; - tokenGasOverrides[1] = DEFAULT_TOKEN_DEST_GAS_OVERHEAD - 1; //invalid token gas override value - gasLimitOverrides[0][0].tokenGasOverrides = tokenGasOverrides; - - vm.expectRevert( - abi.encodeWithSelector( - OffRamp.InvalidManualExecutionTokenGasOverride.selector, - messages[0].header.messageId, - 1, - DEFAULT_TOKEN_DEST_GAS_OVERHEAD, - tokenGasOverrides[1] - ) - ); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - } - - function test_manuallyExecute_FailedTx_Revert() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - - messages[0].receiver = address(s_reverting_receiver); - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - s_offRamp.batchExecute( - _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) - ); - - s_reverting_receiver.setRevert(true); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); - - vm.expectRevert( - abi.encodeWithSelector( - OffRamp.ExecutionError.selector, - messages[0].header.messageId, - abi.encodeWithSelector( - OffRamp.ReceiverError.selector, - abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, bytes("")) - ) - ) - ); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - } - - function test_manuallyExecute_ReentrancyFails_Success() public { - uint256 tokenAmount = 1e9; - IERC20 tokenToAbuse = IERC20(s_destFeeToken); - - // This needs to be deployed before the source chain message is sent - // because we need the address for the receiver. - ReentrancyAbuserMultiRamp receiver = new ReentrancyAbuserMultiRamp(address(s_destRouter), s_offRamp); - uint256 balancePre = tokenToAbuse.balanceOf(address(receiver)); - - // For this test any message will be flagged as correct by the - // commitStore. In a real scenario the abuser would have to actually - // send the message that they want to replay. - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - messages[0].tokenAmounts = new Internal.Any2EVMTokenTransfer[](1); - messages[0].tokenAmounts[0] = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[s_sourceFeeToken]), - destTokenAddress: s_destTokenBySourceToken[s_sourceFeeToken], - extraData: "", - amount: tokenAmount, - destGasAmount: MAX_TOKEN_POOL_RELEASE_OR_MINT_GAS - }); - - messages[0].receiver = address(receiver); - - messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); - - Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - // sets the report to be repeated on the ReentrancyAbuser to be able to replay - receiver.setPayload(report); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); - gasLimitOverrides[0][0].tokenGasOverrides = new uint32[](messages[0].tokenAmounts.length); - - // The first entry should be fine and triggers the second entry which is skipped. Due to the reentrancy - // the second completes first, so we expect the skip event before the success event. - vm.expectEmit(); - emit OffRamp.SkippedAlreadyExecutedMessage( - messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber - ); - - vm.recordLogs(); - s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - // Since the tx failed we don't release the tokens - assertEq(tokenToAbuse.balanceOf(address(receiver)), balancePre + tokenAmount); - } - - function test_manuallyExecute_MultipleReportsWithSingleCursedLane_Revert() public { - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](3); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](2); - - for (uint64 i = 0; i < 3; ++i) { - messages1[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, i + 1); - messages1[i].receiver = address(s_reverting_receiver); - messages1[i].header.messageId = _hashMessage(messages1[i], ON_RAMP_ADDRESS_1); - } - - for (uint64 i = 0; i < 2; ++i) { - messages2[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, i + 1); - messages2[i].receiver = address(s_reverting_receiver); - messages2[i].header.messageId = _hashMessage(messages2[i], ON_RAMP_ADDRESS_3); - } - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](2); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages1); - gasLimitOverrides[1] = _getGasLimitsFromMessages(messages2); - - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_3, true); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.CursedByRMN.selector, SOURCE_CHAIN_SELECTOR_3)); - - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - } - - function test_manuallyExecute_SourceChainSelectorMismatch_Revert() public { - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](1); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); - messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); - - OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](2); - gasLimitOverrides[0] = _getGasLimitsFromMessages(messages1); - gasLimitOverrides[1] = _getGasLimitsFromMessages(messages2); - - vm.expectRevert( - abi.encodeWithSelector( - OffRamp.SourceChainSelectorMismatch.selector, SOURCE_CHAIN_SELECTOR_3, SOURCE_CHAIN_SELECTOR_1 - ) - ); - s_offRamp.manuallyExecute(reports, gasLimitOverrides); - } -} - -contract OffRamp_execute is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); - } - - // Asserts that execute completes - function test_SingleReport_Success() public { - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted( - uint8(Internal.OCRPluginType.Execution), s_configDigestExec, uint64(uint256(s_configDigestExec)) - ); - - vm.recordLogs(); - - _execute(reports); - - assertExecutionStateChangedEventLogs( - SOURCE_CHAIN_SELECTOR_1, - messages[0].header.sequenceNumber, - messages[0].header.messageId, - _hashMessage(messages[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_MultipleReports_Success() public { - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); - - messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); - messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages2); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted( - uint8(Internal.OCRPluginType.Execution), s_configDigestExec, uint64(uint256(s_configDigestExec)) - ); - - vm.recordLogs(); - _execute(reports); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - assertExecutionStateChangedEventLogs( - logs, - messages1[0].header.sourceChainSelector, - messages1[0].header.sequenceNumber, - messages1[0].header.messageId, - _hashMessage(messages1[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - messages1[1].header.sourceChainSelector, - messages1[1].header.sequenceNumber, - messages1[1].header.messageId, - _hashMessage(messages1[1], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - messages2[0].header.sourceChainSelector, - messages2[0].header.sequenceNumber, - messages2[0].header.messageId, - _hashMessage(messages2[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - - function test_LargeBatch_Success() public { - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](10); - for (uint64 i = 0; i < reports.length; ++i) { - Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](3); - messages[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1 + i * 3); - messages[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2 + i * 3); - messages[2] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3 + i * 3); - - reports[i] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - } - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted( - uint8(Internal.OCRPluginType.Execution), s_configDigestExec, uint64(uint256(s_configDigestExec)) - ); - - vm.recordLogs(); - _execute(reports); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - for (uint64 i = 0; i < reports.length; ++i) { - for (uint64 j = 0; j < reports[i].messages.length; ++j) { - assertExecutionStateChangedEventLogs( - logs, - reports[i].messages[j].header.sourceChainSelector, - reports[i].messages[j].header.sequenceNumber, - reports[i].messages[j].header.messageId, - _hashMessage(reports[i].messages[j], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - } - } - } - - function test_MultipleReportsWithPartialValidationFailures_Success() public { - _enableInboundMessageInterceptor(); - - Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); - Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); - - messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); - messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); - messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3); - - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); - reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); - reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages2); - - s_inboundMessageInterceptor.setMessageIdValidationState(messages1[0].header.messageId, true); - s_inboundMessageInterceptor.setMessageIdValidationState(messages2[0].header.messageId, true); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted( - uint8(Internal.OCRPluginType.Execution), s_configDigestExec, uint64(uint256(s_configDigestExec)) - ); - - vm.recordLogs(); - _execute(reports); - - Vm.Log[] memory logs = vm.getRecordedLogs(); - - assertExecutionStateChangedEventLogs( - logs, - messages1[0].header.sourceChainSelector, - messages1[0].header.sequenceNumber, - messages1[0].header.messageId, - _hashMessage(messages1[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector( - IMessageInterceptor.MessageValidationError.selector, - abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) - ) - ); - - assertExecutionStateChangedEventLogs( - logs, - messages1[1].header.sourceChainSelector, - messages1[1].header.sequenceNumber, - messages1[1].header.messageId, - _hashMessage(messages1[1], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.SUCCESS, - "" - ); - - assertExecutionStateChangedEventLogs( - logs, - messages2[0].header.sourceChainSelector, - messages2[0].header.sequenceNumber, - messages2[0].header.messageId, - _hashMessage(messages2[0], ON_RAMP_ADDRESS_1), - Internal.MessageExecutionState.FAILURE, - abi.encodeWithSelector( - IMessageInterceptor.MessageValidationError.selector, - abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) - ) - ); - } - - // Reverts - - function test_UnauthorizedTransmitter_Revert() public { - bytes32[3] memory reportContext = [s_configDigestExec, s_configDigestExec, s_configDigestExec]; - - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); - s_offRamp.execute(reportContext, abi.encode(reports)); - } - - function test_NoConfig_Revert() public { - _redeployOffRampWithNoOCRConfigs(); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); - - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - bytes32[3] memory reportContext = [bytes32(""), s_configDigestExec, s_configDigestExec]; - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); - s_offRamp.execute(reportContext, abi.encode(reports)); - } - - function test_NoConfigWithOtherConfigPresent_Revert() public { - _redeployOffRampWithNoOCRConfigs(); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); - - MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: uint8(Internal.OCRPluginType.Commit), - configDigest: s_configDigestCommit, - F: s_F, - isSignatureVerificationEnabled: true, - signers: s_validSigners, - transmitters: s_validTransmitters - }); - s_offRamp.setOCR3Configs(ocrConfigs); - - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - bytes32[3] memory reportContext = [bytes32(""), s_configDigestExec, s_configDigestExec]; - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); - s_offRamp.execute(reportContext, abi.encode(reports)); - } - - function test_WrongConfigWithSigners_Revert() public { - _redeployOffRampWithNoOCRConfigs(); - s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); - - s_configDigestExec = _getBasicConfigDigest(1, s_validSigners, s_validTransmitters); - - MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: uint8(Internal.OCRPluginType.Execution), - configDigest: s_configDigestExec, - F: s_F, - isSignatureVerificationEnabled: true, - signers: s_validSigners, - transmitters: s_validTransmitters - }); - - vm.expectRevert(OffRamp.SignatureVerificationNotAllowedInExecutionPlugin.selector); - s_offRamp.setOCR3Configs(ocrConfigs); - } - - function test_ZeroReports_Revert() public { - Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](0); - - vm.expectRevert(OffRamp.EmptyBatch.selector); - _execute(reports); - } - - function test_IncorrectArrayType_Revert() public { - bytes32[3] memory reportContext = [s_configDigestExec, s_configDigestExec, s_configDigestExec]; - - uint256[] memory wrongData = new uint256[](2); - wrongData[0] = 1; - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(); - s_offRamp.execute(reportContext, abi.encode(wrongData)); - } - - function test_NonArray_Revert() public { - bytes32[3] memory reportContext = [s_configDigestExec, s_configDigestExec, s_configDigestExec]; - - Internal.Any2EVMRampMessage[] memory messages = - _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); - Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(); - s_offRamp.execute(reportContext, abi.encode(report)); - } -} - -contract OffRamp_getExecutionState is OffRampSetup { - mapping(uint64 sourceChainSelector => mapping(uint64 seqNum => Internal.MessageExecutionState state)) internal - s_differentialExecutionState; - - /// forge-config: default.fuzz.runs = 32 - /// forge-config: ccip.fuzz.runs = 32 - function test_Fuzz_Differential_Success( - uint64 sourceChainSelector, - uint16[500] memory seqNums, - uint8[500] memory values - ) public { - for (uint256 i = 0; i < seqNums.length; ++i) { - // Only use the first three slots. This makes sure existing slots get overwritten - // as the tests uses 500 sequence numbers. - uint16 seqNum = seqNums[i] % 386; - Internal.MessageExecutionState state = Internal.MessageExecutionState(values[i] % 4); - s_differentialExecutionState[sourceChainSelector][seqNum] = state; - s_offRamp.setExecutionStateHelper(sourceChainSelector, seqNum, state); - assertEq(uint256(state), uint256(s_offRamp.getExecutionState(sourceChainSelector, seqNum))); - } - - for (uint256 i = 0; i < seqNums.length; ++i) { - uint16 seqNum = seqNums[i] % 386; - Internal.MessageExecutionState expectedState = s_differentialExecutionState[sourceChainSelector][seqNum]; - assertEq(uint256(expectedState), uint256(s_offRamp.getExecutionState(sourceChainSelector, seqNum))); - } - } - - function test_GetExecutionState_Success() public { - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 0, Internal.MessageExecutionState.FAILURE); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3); - - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 1, Internal.MessageExecutionState.FAILURE); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (3 << 2)); - - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 1, Internal.MessageExecutionState.IN_PROGRESS); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 2)); - - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 2, Internal.MessageExecutionState.FAILURE); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 2) + (3 << 4)); - - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 127, Internal.MessageExecutionState.IN_PROGRESS); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 2) + (3 << 4) + (1 << 254)); - - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 128, Internal.MessageExecutionState.SUCCESS); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 2) + (3 << 4) + (1 << 254)); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 1), 2); - - assertEq( - uint256(Internal.MessageExecutionState.FAILURE), uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 0)) - ); - assertEq( - uint256(Internal.MessageExecutionState.IN_PROGRESS), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 1)) - ); - assertEq( - uint256(Internal.MessageExecutionState.FAILURE), uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 2)) - ); - assertEq( - uint256(Internal.MessageExecutionState.IN_PROGRESS), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 127)) - ); - assertEq( - uint256(Internal.MessageExecutionState.SUCCESS), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 128)) - ); - } - - function test_GetDifferentChainExecutionState_Success() public { - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 0, Internal.MessageExecutionState.FAILURE); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 0), 0); - - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 127, Internal.MessageExecutionState.IN_PROGRESS); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 254)); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 0), 0); - - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 128, Internal.MessageExecutionState.SUCCESS); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 254)); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 1), 2); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 0), 0); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 1), 0); - - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1 + 1, 127, Internal.MessageExecutionState.FAILURE); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 254)); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 1), 2); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 0), (3 << 254)); - assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 1), 0); - - assertEq( - uint256(Internal.MessageExecutionState.FAILURE), uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 0)) - ); - assertEq( - uint256(Internal.MessageExecutionState.IN_PROGRESS), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 127)) - ); - assertEq( - uint256(Internal.MessageExecutionState.SUCCESS), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 128)) - ); - - assertEq( - uint256(Internal.MessageExecutionState.UNTOUCHED), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1 + 1, 0)) - ); - assertEq( - uint256(Internal.MessageExecutionState.FAILURE), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1 + 1, 127)) - ); - assertEq( - uint256(Internal.MessageExecutionState.UNTOUCHED), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1 + 1, 128)) - ); - } - - function test_FillExecutionState_Success() public { - for (uint64 i = 0; i < 384; ++i) { - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, i, Internal.MessageExecutionState.FAILURE); - } - - for (uint64 i = 0; i < 384; ++i) { - assertEq( - uint256(Internal.MessageExecutionState.FAILURE), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, i)) - ); - } - - for (uint64 i = 0; i < 3; ++i) { - assertEq(type(uint256).max, s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, i)); - } - - for (uint64 i = 0; i < 384; ++i) { - s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, i, Internal.MessageExecutionState.IN_PROGRESS); - } - - for (uint64 i = 0; i < 384; ++i) { - assertEq( - uint256(Internal.MessageExecutionState.IN_PROGRESS), - uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, i)) - ); - } - - for (uint64 i = 0; i < 3; ++i) { - // 0x555... == 0b101010101010..... - assertEq( - 0x5555555555555555555555555555555555555555555555555555555555555555, - s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, i) - ); - } - } -} - -contract OffRamp_trialExecute is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - } - - function test_trialExecute_Success() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1000; - amounts[1] = 50; - - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); - IERC20 dstToken0 = IERC20(s_destTokens[0]); - uint256 startingBalance = dstToken0.balanceOf(message.receiver); - - (Internal.MessageExecutionState newState, bytes memory err) = - s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - assertEq(uint256(Internal.MessageExecutionState.SUCCESS), uint256(newState)); - assertEq("", err); - - // Check that the tokens were transferred - assertEq(startingBalance + amounts[0], dstToken0.balanceOf(message.receiver)); - } - - function test_TokenHandlingErrorIsCaught_Success() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1000; - amounts[1] = 50; - - IERC20 dstToken0 = IERC20(s_destTokens[0]); - uint256 startingBalance = dstToken0.balanceOf(OWNER); - - bytes memory errorMessage = "Random token pool issue"; - - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); - s_maybeRevertingPool.setShouldRevert(errorMessage); - - (Internal.MessageExecutionState newState, bytes memory err) = - s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - assertEq(uint256(Internal.MessageExecutionState.FAILURE), uint256(newState)); - assertEq(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, errorMessage), err); - - // Expect the balance to remain the same - assertEq(startingBalance, dstToken0.balanceOf(OWNER)); - } - - function test_RateLimitError_Success() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 1000; - amounts[1] = 50; - - bytes memory errorMessage = abi.encodeWithSelector(RateLimiter.BucketOverfilled.selector); - - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); - s_maybeRevertingPool.setShouldRevert(errorMessage); - - (Internal.MessageExecutionState newState, bytes memory err) = - s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - assertEq(uint256(Internal.MessageExecutionState.FAILURE), uint256(newState)); - assertEq(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, errorMessage), err); - } - - // TODO test actual pool exists but isn't compatible instead of just no pool - function test_TokenPoolIsNotAContract_Success() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 10000; - Internal.Any2EVMRampMessage memory message = - _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); - - // Happy path, pool is correct - (Internal.MessageExecutionState newState, bytes memory err) = - s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - - assertEq(uint256(Internal.MessageExecutionState.SUCCESS), uint256(newState)); - assertEq("", err); - - // address 0 has no contract - assertEq(address(0).code.length, 0); - - message.tokenAmounts[0] = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(address(0)), - destTokenAddress: address(0), - extraData: "", - amount: message.tokenAmounts[0].amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - message.header.messageId = _hashMessage(message, ON_RAMP_ADDRESS_1); - - // Unhappy path, no revert but marked as failed. - (newState, err) = s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - - assertEq(uint256(Internal.MessageExecutionState.FAILURE), uint256(newState)); - assertEq(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, address(0)), err); - - address notAContract = makeAddr("not_a_contract"); - - message.tokenAmounts[0] = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(address(0)), - destTokenAddress: notAContract, - extraData: "", - amount: message.tokenAmounts[0].amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - message.header.messageId = _hashMessage(message, ON_RAMP_ADDRESS_1); - - (newState, err) = s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); - - assertEq(uint256(Internal.MessageExecutionState.FAILURE), uint256(newState)); - assertEq(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, address(0)), err); - } -} - -contract OffRamp_releaseOrMintSingleToken is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - } - - function test__releaseOrMintSingleToken_Success() public { - uint256 amount = 123123; - address token = s_sourceTokens[0]; - bytes memory originalSender = abi.encode(OWNER); - bytes memory offchainTokenData = abi.encode(keccak256("offchainTokenData")); - - IERC20 dstToken1 = IERC20(s_destTokenBySourceToken[token]); - uint256 startingBalance = dstToken1.balanceOf(OWNER); - - Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), - destTokenAddress: s_destTokenBySourceToken[token], - extraData: "", - amount: amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - vm.expectCall( - s_destPoolBySourceToken[token], - abi.encodeWithSelector( - LockReleaseTokenPool.releaseOrMint.selector, - Pool.ReleaseOrMintInV1({ - originalSender: originalSender, - receiver: OWNER, - amount: amount, - localToken: s_destTokenBySourceToken[token], - remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, - sourcePoolAddress: tokenAmount.sourcePoolAddress, - sourcePoolData: tokenAmount.extraData, - offchainTokenData: offchainTokenData - }) - ) - ); - - s_offRamp.releaseOrMintSingleToken(tokenAmount, originalSender, OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData); - - assertEq(startingBalance + amount, dstToken1.balanceOf(OWNER)); - } - - function test_releaseOrMintToken_InvalidDataLength_Revert() public { - uint256 amount = 123123; - address token = s_sourceTokens[0]; - - Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), - destTokenAddress: s_destTokenBySourceToken[token], - extraData: "", - amount: amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - // Mock the call so returns 2 slots of data - vm.mockCall( - s_destTokenBySourceToken[token], abi.encodeWithSelector(IERC20.balanceOf.selector, OWNER), abi.encode(0, 0) - ); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.InvalidDataLength.selector, Internal.MAX_BALANCE_OF_RET_BYTES, 64)); - - s_offRamp.releaseOrMintSingleToken(tokenAmount, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR, ""); - } - - function test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() public { - uint256 amount = 123123; - address token = s_sourceTokens[0]; - - Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), - destTokenAddress: s_destTokenBySourceToken[token], - extraData: "", - amount: amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - bytes memory revertData = "failed to balanceOf"; - - // Mock the call so returns 2 slots of data - vm.mockCallRevert( - s_destTokenBySourceToken[token], abi.encodeWithSelector(IERC20.balanceOf.selector, OWNER), revertData - ); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, revertData)); - - s_offRamp.releaseOrMintSingleToken(tokenAmount, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR, ""); - } - - function test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() public { - uint256 amount = 123123; - address token = s_sourceTokens[0]; - uint256 mockedStaticBalance = 50000; - - Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), - destTokenAddress: s_destTokenBySourceToken[token], - extraData: "", - amount: amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - vm.mockCall( - s_destTokenBySourceToken[token], - abi.encodeWithSelector(IERC20.balanceOf.selector, OWNER), - abi.encode(mockedStaticBalance) - ); - - vm.expectRevert( - abi.encodeWithSelector( - OffRamp.ReleaseOrMintBalanceMismatch.selector, amount, mockedStaticBalance, mockedStaticBalance - ) - ); - - s_offRamp.releaseOrMintSingleToken(tokenAmount, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR, ""); - } - - function test_releaseOrMintToken_skip_ReleaseOrMintBalanceMismatch_if_pool_Revert() public { - uint256 amount = 123123; - address token = s_sourceTokens[0]; - uint256 mockedStaticBalance = 50000; - - Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), - destTokenAddress: s_destTokenBySourceToken[token], - extraData: "", - amount: amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - // This should make the call fail if it does not skip the check - vm.mockCall( - s_destTokenBySourceToken[token], - abi.encodeWithSelector(IERC20.balanceOf.selector, OWNER), - abi.encode(mockedStaticBalance) - ); - - s_offRamp.releaseOrMintSingleToken( - tokenAmount, abi.encode(OWNER), s_destPoolBySourceToken[token], SOURCE_CHAIN_SELECTOR, "" - ); - } - - function test__releaseOrMintSingleToken_NotACompatiblePool_Revert() public { - uint256 amount = 123123; - address token = s_sourceTokens[0]; - address destToken = s_destTokenBySourceToken[token]; - vm.label(destToken, "destToken"); - bytes memory originalSender = abi.encode(OWNER); - bytes memory offchainTokenData = abi.encode(keccak256("offchainTokenData")); - - Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), - destTokenAddress: destToken, - extraData: "", - amount: amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - // Address(0) should always revert - address returnedPool = address(0); - - vm.mockCall( - address(s_tokenAdminRegistry), - abi.encodeWithSelector(ITokenAdminRegistry.getPool.selector, destToken), - abi.encode(returnedPool) - ); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, returnedPool)); - - s_offRamp.releaseOrMintSingleToken(tokenAmount, originalSender, OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData); - - // A contract that doesn't support the interface should also revert - returnedPool = address(s_offRamp); - - vm.mockCall( - address(s_tokenAdminRegistry), - abi.encodeWithSelector(ITokenAdminRegistry.getPool.selector, destToken), - abi.encode(returnedPool) - ); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, returnedPool)); - - s_offRamp.releaseOrMintSingleToken(tokenAmount, originalSender, OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData); - } - - function test__releaseOrMintSingleToken_TokenHandlingError_transfer_Revert() public { - address receiver = makeAddr("receiver"); - uint256 amount = 123123; - address token = s_sourceTokens[0]; - address destToken = s_destTokenBySourceToken[token]; - bytes memory originalSender = abi.encode(OWNER); - bytes memory offchainTokenData = abi.encode(keccak256("offchainTokenData")); - - Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), - destTokenAddress: destToken, - extraData: "", - amount: amount, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - bytes memory revertData = "call reverted :o"; - - vm.mockCallRevert(destToken, abi.encodeWithSelector(IERC20.transfer.selector, receiver, amount), revertData); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, revertData)); - s_offRamp.releaseOrMintSingleToken( - tokenAmount, originalSender, receiver, SOURCE_CHAIN_SELECTOR_1, offchainTokenData - ); - } -} - -contract OffRamp_releaseOrMintTokens is OffRampSetup { - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - } - - function test_releaseOrMintTokens_Success() public { - Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); - IERC20 dstToken1 = IERC20(s_destFeeToken); - uint256 startingBalance = dstToken1.balanceOf(OWNER); - uint256 amount1 = 100; - srcTokenAmounts[0].amount = amount1; - - bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); - offchainTokenData[0] = abi.encode(0x12345678); - - Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); - - vm.expectCall( - s_destPoolBySourceToken[srcTokenAmounts[0].token], - abi.encodeWithSelector( - LockReleaseTokenPool.releaseOrMint.selector, - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: OWNER, - amount: srcTokenAmounts[0].amount, - localToken: s_destTokenBySourceToken[srcTokenAmounts[0].token], - remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, - sourcePoolAddress: sourceTokenAmounts[0].sourcePoolAddress, - sourcePoolData: sourceTokenAmounts[0].extraData, - offchainTokenData: offchainTokenData[0] - }) - ) - ); - - s_offRamp.releaseOrMintTokens( - sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData, new uint32[](0) - ); - - assertEq(startingBalance + amount1, dstToken1.balanceOf(OWNER)); - } - - function test_releaseOrMintTokens_WithGasOverride_Success() public { - Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); - IERC20 dstToken1 = IERC20(s_destFeeToken); - uint256 startingBalance = dstToken1.balanceOf(OWNER); - uint256 amount1 = 100; - srcTokenAmounts[0].amount = amount1; - - bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); - offchainTokenData[0] = abi.encode(0x12345678); - - Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); - - vm.expectCall( - s_destPoolBySourceToken[srcTokenAmounts[0].token], - abi.encodeWithSelector( - LockReleaseTokenPool.releaseOrMint.selector, - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: OWNER, - amount: srcTokenAmounts[0].amount, - localToken: s_destTokenBySourceToken[srcTokenAmounts[0].token], - remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, - sourcePoolAddress: sourceTokenAmounts[0].sourcePoolAddress, - sourcePoolData: sourceTokenAmounts[0].extraData, - offchainTokenData: offchainTokenData[0] - }) - ) - ); - - uint32[] memory gasOverrides = new uint32[](sourceTokenAmounts.length); - for (uint256 i = 0; i < gasOverrides.length; i++) { - gasOverrides[i] = DEFAULT_TOKEN_DEST_GAS_OVERHEAD + 1; - } - s_offRamp.releaseOrMintTokens( - sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData, gasOverrides - ); - - assertEq(startingBalance + amount1, dstToken1.balanceOf(OWNER)); - } - - function test_releaseOrMintTokens_destDenominatedDecimals_Success() public { - Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); - uint256 amount = 100; - uint256 destinationDenominationMultiplier = 1000; - srcTokenAmounts[1].amount = amount; - - bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); - - Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); - - address pool = s_destPoolBySourceToken[srcTokenAmounts[1].token]; - address destToken = s_destTokenBySourceToken[srcTokenAmounts[1].token]; - - MaybeRevertingBurnMintTokenPool(pool).setReleaseOrMintMultiplier(destinationDenominationMultiplier); - - Client.EVMTokenAmount[] memory destTokenAmounts = s_offRamp.releaseOrMintTokens( - sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData, new uint32[](0) - ); - assertEq(destTokenAmounts[1].amount, amount * destinationDenominationMultiplier); - assertEq(destTokenAmounts[1].token, destToken); - } - - // Revert - - function test_TokenHandlingError_Reverts() public { - Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); - - bytes memory unknownError = bytes("unknown error"); - s_maybeRevertingPool.setShouldRevert(unknownError); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, unknownError)); - - s_offRamp.releaseOrMintTokens( - _getDefaultSourceTokenData(srcTokenAmounts), - abi.encode(OWNER), - OWNER, - SOURCE_CHAIN_SELECTOR_1, - new bytes[](srcTokenAmounts.length), - new uint32[](0) - ); - } - - function test_releaseOrMintTokens_InvalidDataLengthReturnData_Revert() public { - uint256 amount = 100; - Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); - srcTokenAmounts[0].amount = amount; - - bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); - Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); - - vm.mockCall( - s_destPoolBySourceToken[srcTokenAmounts[0].token], - abi.encodeWithSelector( - LockReleaseTokenPool.releaseOrMint.selector, - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: OWNER, - amount: amount, - localToken: s_destTokenBySourceToken[srcTokenAmounts[0].token], - remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, - sourcePoolAddress: sourceTokenAmounts[0].sourcePoolAddress, - sourcePoolData: sourceTokenAmounts[0].extraData, - offchainTokenData: offchainTokenData[0] - }) - ), - // Includes the amount twice, this will revert due to the return data being to long - abi.encode(amount, amount) - ); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.InvalidDataLength.selector, Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, 64)); - - s_offRamp.releaseOrMintTokens( - sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData, new uint32[](0) - ); - } - - function test__releaseOrMintTokens_PoolIsNotAPool_Reverts() public { - // The offRamp is a contract, but not a pool - address fakePoolAddress = address(s_offRamp); - - Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = new Internal.Any2EVMTokenTransfer[](1); - sourceTokenAmounts[0] = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: abi.encode(fakePoolAddress), - destTokenAddress: address(s_offRamp), - extraData: "", - amount: 1, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, address(0))); - s_offRamp.releaseOrMintTokens( - sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, new bytes[](1), new uint32[](0) - ); - } - - function test_releaseOrMintTokens_PoolDoesNotSupportDest_Reverts() public { - Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); - uint256 amount1 = 100; - srcTokenAmounts[0].amount = amount1; - - bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); - offchainTokenData[0] = abi.encode(0x12345678); - - Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); - - vm.expectCall( - s_destPoolBySourceToken[srcTokenAmounts[0].token], - abi.encodeWithSelector( - LockReleaseTokenPool.releaseOrMint.selector, - Pool.ReleaseOrMintInV1({ - originalSender: abi.encode(OWNER), - receiver: OWNER, - amount: srcTokenAmounts[0].amount, - localToken: s_destTokenBySourceToken[srcTokenAmounts[0].token], - remoteChainSelector: SOURCE_CHAIN_SELECTOR_3, - sourcePoolAddress: sourceTokenAmounts[0].sourcePoolAddress, - sourcePoolData: sourceTokenAmounts[0].extraData, - offchainTokenData: offchainTokenData[0] - }) - ) - ); - vm.expectRevert(); - s_offRamp.releaseOrMintTokens( - sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_3, offchainTokenData, new uint32[](0) - ); - } - - /// forge-config: default.fuzz.runs = 32 - /// forge-config: ccip.fuzz.runs = 1024 - // Uint256 gives a good range of values to test, both inside and outside of the eth address space. - function test_Fuzz__releaseOrMintTokens_AnyRevertIsCaught_Success( - address destPool - ) public { - // Input 447301751254033913445893214690834296930546521452, which is 0x4E59B44847B379578588920CA78FBF26C0B4956C - // triggers some Create2Deployer and causes it to fail - vm.assume(destPool != 0x4e59b44847b379578588920cA78FbF26c0B4956C); - bytes memory unusedVar = abi.encode(makeAddr("unused")); - Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = new Internal.Any2EVMTokenTransfer[](1); - sourceTokenAmounts[0] = Internal.Any2EVMTokenTransfer({ - sourcePoolAddress: unusedVar, - destTokenAddress: destPool, - extraData: unusedVar, - amount: 1, - destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD - }); - - try s_offRamp.releaseOrMintTokens( - sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, new bytes[](1), new uint32[](0) - ) {} catch (bytes memory reason) { - // Any revert should be a TokenHandlingError, InvalidEVMAddress, InvalidDataLength or NoContract as those are caught by the offramp - assertTrue( - bytes4(reason) == OffRamp.TokenHandlingError.selector || bytes4(reason) == Internal.InvalidEVMAddress.selector - || bytes4(reason) == OffRamp.InvalidDataLength.selector - || bytes4(reason) == CallWithExactGas.NoContract.selector - || bytes4(reason) == OffRamp.NotACompatiblePool.selector, - "Expected TokenHandlingError or InvalidEVMAddress" - ); - - if (uint160(destPool) > type(uint160).max) { - assertEq(reason, abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, abi.encode(destPool))); - } - } - } -} - -contract OffRamp_applySourceChainConfigUpdates is OffRampSetup { - function test_ApplyZeroUpdates_Success() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); - - vm.recordLogs(); - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - // No logs emitted - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 0); - - assertEq(s_offRamp.getSourceChainSelectors().length, 0); - } - - function test_AddNewChain_Success() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - - OffRamp.SourceChainConfig memory expectedSourceChainConfig = - OffRamp.SourceChainConfig({router: s_destRouter, isEnabled: true, minSeqNr: 1, onRamp: ON_RAMP_ADDRESS_1}); - - vm.expectEmit(); - emit OffRamp.SourceChainSelectorAdded(SOURCE_CHAIN_SELECTOR_1); - - vm.expectEmit(); - emit OffRamp.SourceChainConfigSet(SOURCE_CHAIN_SELECTOR_1, expectedSourceChainConfig); - - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - _assertSourceChainConfigEquality(s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR_1), expectedSourceChainConfig); - } - - function test_ReplaceExistingChain_Success() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - sourceChainConfigs[0].isEnabled = false; - OffRamp.SourceChainConfig memory expectedSourceChainConfig = - OffRamp.SourceChainConfig({router: s_destRouter, isEnabled: false, minSeqNr: 1, onRamp: ON_RAMP_ADDRESS_1}); - - vm.expectEmit(); - emit OffRamp.SourceChainConfigSet(SOURCE_CHAIN_SELECTOR_1, expectedSourceChainConfig); - - vm.recordLogs(); - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - // No log emitted for chain selector added (only for setting the config) - Vm.Log[] memory logEntries = vm.getRecordedLogs(); - assertEq(logEntries.length, 1); - - _assertSourceChainConfigEquality(s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR_1), expectedSourceChainConfig); - - uint256[] memory resultSourceChainSelectors = s_offRamp.getSourceChainSelectors(); - assertEq(resultSourceChainSelectors.length, 1); - } - - function test_AddMultipleChains_Success() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](3); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: abi.encode(ON_RAMP_ADDRESS_1, 0), - isEnabled: true - }); - sourceChainConfigs[1] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1 + 1, - onRamp: abi.encode(ON_RAMP_ADDRESS_1, 1), - isEnabled: false - }); - sourceChainConfigs[2] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1 + 2, - onRamp: abi.encode(ON_RAMP_ADDRESS_1, 2), - isEnabled: true - }); - - OffRamp.SourceChainConfig[] memory expectedSourceChainConfigs = new OffRamp.SourceChainConfig[](3); - for (uint256 i = 0; i < 3; ++i) { - expectedSourceChainConfigs[i] = OffRamp.SourceChainConfig({ - router: s_destRouter, - isEnabled: sourceChainConfigs[i].isEnabled, - minSeqNr: 1, - onRamp: abi.encode(ON_RAMP_ADDRESS_1, i) - }); - - vm.expectEmit(); - emit OffRamp.SourceChainSelectorAdded(sourceChainConfigs[i].sourceChainSelector); - - vm.expectEmit(); - emit OffRamp.SourceChainConfigSet(sourceChainConfigs[i].sourceChainSelector, expectedSourceChainConfigs[i]); - } - - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - for (uint256 i = 0; i < 3; ++i) { - _assertSourceChainConfigEquality( - s_offRamp.getSourceChainConfig(sourceChainConfigs[i].sourceChainSelector), expectedSourceChainConfigs[i] - ); - } - } - - // Setting lower fuzz run as 256 runs was sometimes resulting in flakes. - /// forge-config: default.fuzz.runs = 32 - /// forge-config: ccip.fuzz.runs = 32 - function test_Fuzz_applySourceChainConfigUpdate_Success( - OffRamp.SourceChainConfigArgs memory sourceChainConfigArgs - ) public { - // Skip invalid inputs - vm.assume(sourceChainConfigArgs.sourceChainSelector != 0); - vm.assume(sourceChainConfigArgs.onRamp.length != 0); - vm.assume(address(sourceChainConfigArgs.router) != address(0)); - - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](2); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - sourceChainConfigs[1] = sourceChainConfigArgs; - - // Handle cases when an update occurs - bool isNewChain = sourceChainConfigs[1].sourceChainSelector != SOURCE_CHAIN_SELECTOR_1; - if (!isNewChain) { - sourceChainConfigs[1].onRamp = sourceChainConfigs[0].onRamp; - } - - OffRamp.SourceChainConfig memory expectedSourceChainConfig = OffRamp.SourceChainConfig({ - router: sourceChainConfigArgs.router, - isEnabled: sourceChainConfigArgs.isEnabled, - minSeqNr: 1, - onRamp: sourceChainConfigArgs.onRamp - }); - - if (isNewChain) { - vm.expectEmit(); - emit OffRamp.SourceChainSelectorAdded(sourceChainConfigArgs.sourceChainSelector); - } - - vm.expectEmit(); - emit OffRamp.SourceChainConfigSet(sourceChainConfigArgs.sourceChainSelector, expectedSourceChainConfig); - - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - _assertSourceChainConfigEquality( - s_offRamp.getSourceChainConfig(sourceChainConfigArgs.sourceChainSelector), expectedSourceChainConfig - ); - } - - function test_ReplaceExistingChainOnRamp_Success() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - sourceChainConfigs[0].onRamp = ON_RAMP_ADDRESS_2; - - vm.expectEmit(); - emit OffRamp.SourceChainConfigSet( - SOURCE_CHAIN_SELECTOR_1, - OffRamp.SourceChainConfig({router: s_destRouter, isEnabled: true, minSeqNr: 1, onRamp: ON_RAMP_ADDRESS_2}) - ); - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - } - - function test_allowNonOnRampUpdateAfterLaneIsUsed_success() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: 2, - merkleRoot: "test #2" - }); - - _commit( - OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }), - s_latestSequenceNumber - ); - - vm.startPrank(OWNER); - - // Allow changes to the Router even after the seqNum is not 1 - assertGt(s_offRamp.getSourceChainConfig(sourceChainConfigs[0].sourceChainSelector).minSeqNr, 1); - - sourceChainConfigs[0].router = IRouter(makeAddr("newRouter")); - - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - } - - // Reverts - - function test_ZeroOnRampAddress_Revert() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: new bytes(0), - isEnabled: true - }); - - vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - sourceChainConfigs[0].onRamp = abi.encode(address(0)); - vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - } - - function test_RouterAddress_Revert() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: IRouter(address(0)), - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - - vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - } - - function test_ZeroSourceChainSelector_Revert() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: 0, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - - vm.expectRevert(OffRamp.ZeroChainSelectorNotAllowed.selector); - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - } - - function test_InvalidOnRampUpdate_Revert() public { - OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); - sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ - router: s_destRouter, - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRamp: ON_RAMP_ADDRESS_1, - isEnabled: true - }); - - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: 2, - merkleRoot: "test #2" - }); - - _commit( - OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }), - s_latestSequenceNumber - ); - - vm.stopPrank(); - vm.startPrank(OWNER); - - sourceChainConfigs[0].onRamp = ON_RAMP_ADDRESS_2; - - vm.expectRevert(abi.encodeWithSelector(OffRamp.InvalidOnRampUpdate.selector, SOURCE_CHAIN_SELECTOR_1)); - s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); - } -} - -contract OffRamp_commit is OffRampSetup { - uint64 internal s_maxInterval = 12; - - function setUp() public virtual override { - super.setUp(); - _setupMultipleOffRamps(); - - s_latestSequenceNumber = uint64(uint256(s_configDigestCommit)); - } - - function test_ReportAndPriceUpdate_Success() public { - OffRamp.CommitReport memory commitReport = _constructCommitReport(); - - vm.expectEmit(); - emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - - assertEq(s_maxInterval + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); - assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); - } - - function test_ReportOnlyRootSuccess_gas() public { - uint64 max1 = 931; - bytes32 root = "Only a single root"; - - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: max1, - merkleRoot: root - }); - - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - vm.expectEmit(); - emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - - assertEq(max1 + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); - assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); - assertEq(block.timestamp, s_offRamp.getMerkleRoot(SOURCE_CHAIN_SELECTOR_1, root)); - } - - function test_RootWithRMNDisabled_success() public { - // force RMN verification to fail - vm.mockCallRevert(address(s_mockRMNRemote), abi.encodeWithSelector(IRMNRemote.verify.selector), bytes("")); - - // but ☝️ doesn't matter because RMN verification is disabled - OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); - dynamicConfig.isRMNVerificationDisabled = true; - s_offRamp.setDynamicConfig(dynamicConfig); - - uint64 max1 = 931; - bytes32 root = "Only a single root"; - - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: max1, - merkleRoot: root - }); - - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - vm.expectEmit(); - emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - - assertEq(max1 + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); - assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); - assertEq(block.timestamp, s_offRamp.getMerkleRoot(SOURCE_CHAIN_SELECTOR_1, root)); - } - - function test_StaleReportWithRoot_Success() public { - uint64 maxSeq = 12; - uint224 tokenStartPrice = IFeeQuoter(s_offRamp.getDynamicConfig().feeQuoter).getTokenPrice(s_sourceFeeToken).value; - - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: maxSeq, - merkleRoot: "stale report 1" - }); - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - vm.expectEmit(); - emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - - assertEq(maxSeq + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); - assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); - - commitReport.merkleRoots[0].minSeqNr = maxSeq + 1; - commitReport.merkleRoots[0].maxSeqNr = maxSeq * 2; - commitReport.merkleRoots[0].merkleRoot = "stale report 2"; - - vm.expectEmit(); - emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - - assertEq(maxSeq * 2 + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); - assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); - assertEq(tokenStartPrice, IFeeQuoter(s_offRamp.getDynamicConfig().feeQuoter).getTokenPrice(s_sourceFeeToken).value); - } - - function test_OnlyTokenPriceUpdates_Success() public { - // force RMN verification to fail - vm.mockCallRevert(address(s_mockRMNRemote), abi.encodeWithSelector(IRMNRemote.verify.selector), bytes("")); - - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); - OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - - assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); - } - - function test_OnlyGasPriceUpdates_Success() public { - // force RMN verification to fail - vm.mockCallRevert(address(s_mockRMNRemote), abi.encodeWithSelector(IRMNRemote.verify.selector), bytes("")); - - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); - OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); - } - - function test_PriceSequenceNumberCleared_Success() public { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); - OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); - _commit(commitReport, s_latestSequenceNumber); - - assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); - - vm.startPrank(OWNER); - MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: uint8(Internal.OCRPluginType.Execution), - configDigest: s_configDigestExec, - F: s_F, - isSignatureVerificationEnabled: false, - signers: s_emptySigners, - transmitters: s_validTransmitters - }); - s_offRamp.setOCR3Configs(ocrConfigs); - - // Execution plugin OCR config should not clear latest epoch and round - assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); - - // Commit plugin config should clear latest epoch & round - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: uint8(Internal.OCRPluginType.Commit), - configDigest: s_configDigestCommit, - F: s_F, - isSignatureVerificationEnabled: true, - signers: s_validSigners, - transmitters: s_validTransmitters - }); - s_offRamp.setOCR3Configs(ocrConfigs); - - assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); - - // The same sequence number can be reported again - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); - - _commit(commitReport, s_latestSequenceNumber); - } - - function test_ValidPriceUpdateThenStaleReportWithRoot_Success() public { - uint64 maxSeq = 12; - uint224 tokenPrice1 = 4e18; - uint224 tokenPrice2 = 5e18; - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); - OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, tokenPrice1), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, tokenPrice1, block.timestamp); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); - - roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: maxSeq, - merkleRoot: "stale report" - }); - commitReport.priceUpdates = _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, tokenPrice2); - commitReport.merkleRoots = roots; - - vm.expectEmit(); - emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); - - vm.expectEmit(); - emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); - - _commit(commitReport, s_latestSequenceNumber); - - assertEq(maxSeq + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); - assertEq(tokenPrice1, IFeeQuoter(s_offRamp.getDynamicConfig().feeQuoter).getTokenPrice(s_sourceFeeToken).value); - assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); - } - - // Reverts - - function test_UnauthorizedTransmitter_Revert() public { - OffRamp.CommitReport memory commitReport = _constructCommitReport(); - - bytes32[3] memory reportContext = - [s_configDigestCommit, bytes32(uint256(s_latestSequenceNumber)), s_configDigestCommit]; - - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, abi.encode(commitReport), reportContext, s_F + 1); - - vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); - s_offRamp.commit(reportContext, abi.encode(commitReport), rs, ss, rawVs); - } - - function test_NoConfig_Revert() public { - _redeployOffRampWithNoOCRConfigs(); - - OffRamp.CommitReport memory commitReport = _constructCommitReport(); - - bytes32[3] memory reportContext = [bytes32(""), s_configDigestCommit, s_configDigestCommit]; - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, abi.encode(commitReport), reportContext, s_F + 1); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(); - s_offRamp.commit(reportContext, abi.encode(commitReport), rs, ss, rawVs); - } - - function test_NoConfigWithOtherConfigPresent_Revert() public { - _redeployOffRampWithNoOCRConfigs(); - - MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: uint8(Internal.OCRPluginType.Execution), - configDigest: s_configDigestExec, - F: s_F, - isSignatureVerificationEnabled: false, - signers: s_emptySigners, - transmitters: s_validTransmitters - }); - s_offRamp.setOCR3Configs(ocrConfigs); - - OffRamp.CommitReport memory commitReport = _constructCommitReport(); - - bytes32[3] memory reportContext = [bytes32(""), s_configDigestCommit, s_configDigestCommit]; - (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, abi.encode(commitReport), reportContext, s_F + 1); - - vm.startPrank(s_validTransmitters[0]); - vm.expectRevert(); - s_offRamp.commit(reportContext, abi.encode(commitReport), rs, ss, rawVs); - } - - function test_FailedRMNVerification_Reverts() public { - // force RMN verification to fail - vm.mockCallRevert(address(s_mockRMNRemote), abi.encodeWithSelector(IRMNRemote.verify.selector), bytes("")); - - OffRamp.CommitReport memory commitReport = _constructCommitReport(); - vm.expectRevert(); - _commit(commitReport, s_latestSequenceNumber); - } - - function test_Unhealthy_Revert() public { - _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - minSeqNr: 1, - maxSeqNr: 2, - merkleRoot: "Only a single root", - onRampAddress: abi.encode(ON_RAMP_ADDRESS_1) - }); - - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.CursedByRMN.selector, roots[0].sourceChainSelector)); - _commit(commitReport, s_latestSequenceNumber); - } - - function test_InvalidRootRevert() public { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: 4, - merkleRoot: bytes32(0) - }); - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - vm.expectRevert(OffRamp.InvalidRoot.selector); - _commit(commitReport, s_latestSequenceNumber); - } - - function test_InvalidInterval_Revert() public { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 2, - maxSeqNr: 2, - merkleRoot: bytes32(0) - }); - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - vm.expectRevert( - abi.encodeWithSelector( - OffRamp.InvalidInterval.selector, roots[0].sourceChainSelector, roots[0].minSeqNr, roots[0].maxSeqNr - ) - ); - _commit(commitReport, s_latestSequenceNumber); - } - - function test_InvalidIntervalMinLargerThanMax_Revert() public { - s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR); - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: 0, - merkleRoot: bytes32(0) - }); - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - vm.expectRevert( - abi.encodeWithSelector( - OffRamp.InvalidInterval.selector, roots[0].sourceChainSelector, roots[0].minSeqNr, roots[0].maxSeqNr - ) - ); - _commit(commitReport, s_latestSequenceNumber); - } - - function test_ZeroEpochAndRound_Revert() public { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); - OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }); - - vm.expectRevert(OffRamp.StaleCommitReport.selector); - _commit(commitReport, 0); - } - - function test_OnlyPriceUpdateStaleReport_Revert() public { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); - OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }); - - vm.expectEmit(); - emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); - _commit(commitReport, s_latestSequenceNumber); - - vm.expectRevert(OffRamp.StaleCommitReport.selector); - _commit(commitReport, s_latestSequenceNumber); - } - - function test_SourceChainNotEnabled_Revert() public { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: 0, - onRampAddress: abi.encode(ON_RAMP_ADDRESS_1), - minSeqNr: 1, - maxSeqNr: 2, - merkleRoot: "Only a single root" - }); - - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - vm.expectRevert(abi.encodeWithSelector(OffRamp.SourceChainNotEnabled.selector, 0)); - _commit(commitReport, s_latestSequenceNumber); - } - - function test_RootAlreadyCommitted_Revert() public { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: 2, - merkleRoot: "Only a single root" - }); - OffRamp.CommitReport memory commitReport = - OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); - - _commit(commitReport, s_latestSequenceNumber); - commitReport.merkleRoots[0].minSeqNr = 3; - commitReport.merkleRoots[0].maxSeqNr = 3; - - vm.expectRevert( - abi.encodeWithSelector(OffRamp.RootAlreadyCommitted.selector, roots[0].sourceChainSelector, roots[0].merkleRoot) - ); - _commit(commitReport, ++s_latestSequenceNumber); - } - - function test_CommitOnRampMismatch_Revert() public { - OffRamp.CommitReport memory commitReport = _constructCommitReport(); - - commitReport.merkleRoots[0].onRampAddress = ON_RAMP_ADDRESS_2; - - vm.expectRevert(abi.encodeWithSelector(OffRamp.CommitOnRampMismatch.selector, ON_RAMP_ADDRESS_2, ON_RAMP_ADDRESS_1)); - _commit(commitReport, s_latestSequenceNumber); - } - - function _constructCommitReport() internal view returns (OffRamp.CommitReport memory) { - Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); - roots[0] = Internal.MerkleRoot({ - sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, - onRampAddress: ON_RAMP_ADDRESS_1, - minSeqNr: 1, - maxSeqNr: s_maxInterval, - merkleRoot: "test #2" - }); - - return OffRamp.CommitReport({ - priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), - merkleRoots: roots, - rmnSignatures: s_rmnSignatures - }); - } -} - -contract OffRamp_afterOC3ConfigSet is OffRampSetup { - function test_afterOCR3ConfigSet_SignatureVerificationDisabled_Revert() public { - s_offRamp = new OffRampHelper( - OffRamp.StaticConfig({ - chainSelector: DEST_CHAIN_SELECTOR, - rmnRemote: s_mockRMNRemote, - tokenAdminRegistry: address(s_tokenAdminRegistry), - nonceManager: address(s_inboundNonceManager) - }), - _generateDynamicOffRampConfig(address(s_feeQuoter)), - new OffRamp.SourceChainConfigArgs[](0) - ); - - MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); - ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ - ocrPluginType: uint8(Internal.OCRPluginType.Commit), - configDigest: s_configDigestCommit, - F: s_F, - isSignatureVerificationEnabled: false, - signers: s_validSigners, - transmitters: s_validTransmitters - }); - - vm.expectRevert(OffRamp.SignatureVerificationRequiredInCommitPlugin.selector); - s_offRamp.setOCR3Configs(ocrConfigs); - } -} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.afterOC3ConfigSet.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.afterOC3ConfigSet.t.sol new file mode 100644 index 00000000000..91694dbcb05 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.afterOC3ConfigSet.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../../libraries/Internal.sol"; +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {OffRampHelper} from "../../helpers/OffRampHelper.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +contract OffRamp_afterOC3ConfigSet is OffRampSetup { + function test_afterOCR3ConfigSet_SignatureVerificationDisabled_Revert() public { + s_offRamp = new OffRampHelper( + OffRamp.StaticConfig({ + chainSelector: DEST_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + tokenAdminRegistry: address(s_tokenAdminRegistry), + nonceManager: address(s_inboundNonceManager) + }), + _generateDynamicOffRampConfig(address(s_feeQuoter)), + new OffRamp.SourceChainConfigArgs[](0) + ); + + MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: uint8(Internal.OCRPluginType.Commit), + configDigest: s_configDigestCommit, + F: F, + isSignatureVerificationEnabled: false, + signers: s_validSigners, + transmitters: s_validTransmitters + }); + + vm.expectRevert(OffRamp.SignatureVerificationRequiredInCommitPlugin.selector); + s_offRamp.setOCR3Configs(ocrConfigs); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.applySourceChainConfigUpdates.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.applySourceChainConfigUpdates.t.sol new file mode 100644 index 00000000000..7ed5c22b800 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.applySourceChainConfigUpdates.t.sol @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRouter} from "../../../interfaces/IRouter.sol"; + +import {Internal} from "../../../libraries/Internal.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +import {Vm} from "forge-std/Vm.sol"; + +contract OffRamp_applySourceChainConfigUpdates is OffRampSetup { + function test_ApplyZeroUpdates_Success() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); + + vm.recordLogs(); + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + // No logs emitted + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 0); + + assertEq(s_offRamp.getSourceChainSelectors().length, 0); + } + + function test_AddNewChain_Success() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + + OffRamp.SourceChainConfig memory expectedSourceChainConfig = + OffRamp.SourceChainConfig({router: s_destRouter, isEnabled: true, minSeqNr: 1, onRamp: ON_RAMP_ADDRESS_1}); + + vm.expectEmit(); + emit OffRamp.SourceChainSelectorAdded(SOURCE_CHAIN_SELECTOR_1); + + vm.expectEmit(); + emit OffRamp.SourceChainConfigSet(SOURCE_CHAIN_SELECTOR_1, expectedSourceChainConfig); + + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + _assertSourceChainConfigEquality(s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR_1), expectedSourceChainConfig); + } + + function test_ReplaceExistingChain_Success() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + sourceChainConfigs[0].isEnabled = false; + OffRamp.SourceChainConfig memory expectedSourceChainConfig = + OffRamp.SourceChainConfig({router: s_destRouter, isEnabled: false, minSeqNr: 1, onRamp: ON_RAMP_ADDRESS_1}); + + vm.expectEmit(); + emit OffRamp.SourceChainConfigSet(SOURCE_CHAIN_SELECTOR_1, expectedSourceChainConfig); + + vm.recordLogs(); + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + // No log emitted for chain selector added (only for setting the config) + Vm.Log[] memory logEntries = vm.getRecordedLogs(); + assertEq(logEntries.length, 1); + + _assertSourceChainConfigEquality(s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR_1), expectedSourceChainConfig); + + uint256[] memory resultSourceChainSelectors = s_offRamp.getSourceChainSelectors(); + assertEq(resultSourceChainSelectors.length, 1); + } + + function test_AddMultipleChains_Success() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](3); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: abi.encode(ON_RAMP_ADDRESS_1, 0), + isEnabled: true + }); + sourceChainConfigs[1] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1 + 1, + onRamp: abi.encode(ON_RAMP_ADDRESS_1, 1), + isEnabled: false + }); + sourceChainConfigs[2] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1 + 2, + onRamp: abi.encode(ON_RAMP_ADDRESS_1, 2), + isEnabled: true + }); + + OffRamp.SourceChainConfig[] memory expectedSourceChainConfigs = new OffRamp.SourceChainConfig[](3); + for (uint256 i = 0; i < 3; ++i) { + expectedSourceChainConfigs[i] = OffRamp.SourceChainConfig({ + router: s_destRouter, + isEnabled: sourceChainConfigs[i].isEnabled, + minSeqNr: 1, + onRamp: abi.encode(ON_RAMP_ADDRESS_1, i) + }); + + vm.expectEmit(); + emit OffRamp.SourceChainSelectorAdded(sourceChainConfigs[i].sourceChainSelector); + + vm.expectEmit(); + emit OffRamp.SourceChainConfigSet(sourceChainConfigs[i].sourceChainSelector, expectedSourceChainConfigs[i]); + } + + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + for (uint256 i = 0; i < 3; ++i) { + _assertSourceChainConfigEquality( + s_offRamp.getSourceChainConfig(sourceChainConfigs[i].sourceChainSelector), expectedSourceChainConfigs[i] + ); + } + } + + // Setting lower fuzz run as 256 runs was sometimes resulting in flakes. + /// forge-config: default.fuzz.runs = 32 + /// forge-config: ccip.fuzz.runs = 32 + function test_Fuzz_applySourceChainConfigUpdate_Success( + OffRamp.SourceChainConfigArgs memory sourceChainConfigArgs + ) public { + // Skip invalid inputs + vm.assume(sourceChainConfigArgs.sourceChainSelector != 0); + vm.assume(sourceChainConfigArgs.onRamp.length != 0); + vm.assume(address(sourceChainConfigArgs.router) != address(0)); + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](2); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + sourceChainConfigs[1] = sourceChainConfigArgs; + + // Handle cases when an update occurs + bool isNewChain = sourceChainConfigs[1].sourceChainSelector != SOURCE_CHAIN_SELECTOR_1; + if (!isNewChain) { + sourceChainConfigs[1].onRamp = sourceChainConfigs[0].onRamp; + } + + OffRamp.SourceChainConfig memory expectedSourceChainConfig = OffRamp.SourceChainConfig({ + router: sourceChainConfigArgs.router, + isEnabled: sourceChainConfigArgs.isEnabled, + minSeqNr: 1, + onRamp: sourceChainConfigArgs.onRamp + }); + + if (isNewChain) { + vm.expectEmit(); + emit OffRamp.SourceChainSelectorAdded(sourceChainConfigArgs.sourceChainSelector); + } + + vm.expectEmit(); + emit OffRamp.SourceChainConfigSet(sourceChainConfigArgs.sourceChainSelector, expectedSourceChainConfig); + + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + _assertSourceChainConfigEquality( + s_offRamp.getSourceChainConfig(sourceChainConfigArgs.sourceChainSelector), expectedSourceChainConfig + ); + } + + function test_ReplaceExistingChainOnRamp_Success() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + sourceChainConfigs[0].onRamp = ON_RAMP_ADDRESS_2; + + vm.expectEmit(); + emit OffRamp.SourceChainConfigSet( + SOURCE_CHAIN_SELECTOR_1, + OffRamp.SourceChainConfig({router: s_destRouter, isEnabled: true, minSeqNr: 1, onRamp: ON_RAMP_ADDRESS_2}) + ); + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + } + + function test_allowNonOnRampUpdateAfterLaneIsUsed_success() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: 2, + merkleRoot: "test #2" + }); + + _commit( + OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }), + s_latestSequenceNumber + ); + + vm.startPrank(OWNER); + + // Allow changes to the Router even after the seqNum is not 1 + assertGt(s_offRamp.getSourceChainConfig(sourceChainConfigs[0].sourceChainSelector).minSeqNr, 1); + + sourceChainConfigs[0].router = IRouter(makeAddr("newRouter")); + + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + } + + // Reverts + + function test_ZeroOnRampAddress_Revert() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: new bytes(0), + isEnabled: true + }); + + vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + sourceChainConfigs[0].onRamp = abi.encode(address(0)); + vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + } + + function test_RouterAddress_Revert() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: IRouter(address(0)), + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + + vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + } + + function test_ZeroSourceChainSelector_Revert() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: 0, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + + vm.expectRevert(OffRamp.ZeroChainSelectorNotAllowed.selector); + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + } + + function test_InvalidOnRampUpdate_Revert() public { + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: 2, + merkleRoot: "test #2" + }); + + _commit( + OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }), + s_latestSequenceNumber + ); + + vm.stopPrank(); + vm.startPrank(OWNER); + + sourceChainConfigs[0].onRamp = ON_RAMP_ADDRESS_2; + + vm.expectRevert(abi.encodeWithSelector(OffRamp.InvalidOnRampUpdate.selector, SOURCE_CHAIN_SELECTOR_1)); + s_offRamp.applySourceChainConfigUpdates(sourceChainConfigs); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol new file mode 100644 index 00000000000..6dade484aee --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.batchExecute.t.sol @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../../libraries/Internal.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +import {Vm} from "forge-std/Vm.sol"; + +contract OffRamp_batchExecute is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_3, 1); + } + + function test_SingleReport_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); + + vm.recordLogs(); + s_offRamp.batchExecute( + _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertGt(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender), nonceBefore); + } + + function test_MultipleReportsSameChain_Success() public { + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); + + messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); + messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages2); + + uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages1[0].sender); + vm.recordLogs(); + s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertExecutionStateChangedEventLogs( + logs, + messages1[0].header.sourceChainSelector, + messages1[0].header.sequenceNumber, + messages1[0].header.messageId, + _hashMessage(messages1[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + messages1[1].header.sourceChainSelector, + messages1[1].header.sequenceNumber, + messages1[1].header.messageId, + _hashMessage(messages1[1], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + messages2[0].header.sourceChainSelector, + messages2[0].header.sequenceNumber, + messages2[0].header.messageId, + _hashMessage(messages2[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertGt(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages1[0].sender), nonceBefore); + } + + function test_MultipleReportsDifferentChains_Success() public { + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); + + messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); + messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, 1); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); + + vm.recordLogs(); + + s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + assertExecutionStateChangedEventLogs( + logs, + messages1[0].header.sourceChainSelector, + messages1[0].header.sequenceNumber, + messages1[0].header.messageId, + _hashMessage(messages1[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + messages1[1].header.sourceChainSelector, + messages1[1].header.sequenceNumber, + messages1[1].header.messageId, + _hashMessage(messages1[1], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + messages2[0].header.sourceChainSelector, + messages2[0].header.sequenceNumber, + messages2[0].header.messageId, + _hashMessage(messages2[0], ON_RAMP_ADDRESS_3), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + uint64 nonceChain1 = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages1[0].sender); + uint64 nonceChain3 = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, messages2[0].sender); + + assertTrue(nonceChain1 != nonceChain3); + assertGt(nonceChain1, 0); + assertGt(nonceChain3, 0); + } + + function test_MultipleReportsDifferentChainsSkipCursedChain_Success() public { + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); + + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); + + messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); + messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, 1); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); + + vm.recordLogs(); + + vm.expectEmit(); + emit OffRamp.SkippedReportExecution(SOURCE_CHAIN_SELECTOR_1); + + s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + for (uint256 i = 0; i < logs.length; ++i) { + if (logs[i].topics[0] == OffRamp.ExecutionStateChanged.selector) { + uint64 logSourceChainSelector = uint64(uint256(logs[i].topics[1])); + uint64 logSequenceNumber = uint64(uint256(logs[i].topics[2])); + bytes32 logMessageId = bytes32(logs[i].topics[3]); + (bytes32 logMessageHash, uint8 logState,,) = abi.decode(logs[i].data, (bytes32, uint8, bytes, uint256)); + assertEq(logMessageId, messages2[0].header.messageId); + assertEq(logSourceChainSelector, messages2[0].header.sourceChainSelector); + assertEq(logSequenceNumber, messages2[0].header.sequenceNumber); + assertEq(logMessageId, messages2[0].header.messageId); + assertEq(logMessageHash, _hashMessage(messages2[0], ON_RAMP_ADDRESS_3)); + assertEq(logState, uint8(Internal.MessageExecutionState.SUCCESS)); + } + } + } + + function test_MultipleReportsSkipDuplicate_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + vm.expectEmit(); + emit OffRamp.SkippedAlreadyExecutedMessage(SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber); + + vm.recordLogs(); + s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_Unhealthy_Success() public { + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); + vm.expectEmit(); + emit OffRamp.SkippedReportExecution(SOURCE_CHAIN_SELECTOR_1); + s_offRamp.batchExecute( + _generateBatchReportFromMessages( + SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) + ), + new OffRamp.GasLimitOverride[][](1) + ); + + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, false); + + vm.recordLogs(); + s_offRamp.batchExecute( + _generateBatchReportFromMessages( + SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) + ), + new OffRamp.GasLimitOverride[][](1) + ); + + _assertNoEmit(OffRamp.SkippedReportExecution.selector); + } + + // Reverts + function test_ZeroReports_Revert() public { + vm.expectRevert(OffRamp.EmptyBatch.selector); + s_offRamp.batchExecute(new Internal.ExecutionReport[](0), new OffRamp.GasLimitOverride[][](1)); + } + + function test_OutOfBoundsGasLimitsAccess_Revert() public { + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); + + messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); + messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages2); + + vm.expectRevert(); + s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](1)); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol new file mode 100644 index 00000000000..f4e6be1b8aa --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.ccipReceive.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Client} from "../../../libraries/Client.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +contract OffRamp_ccipReceive is OffRampSetup { + function test_RevertWhen_Always() public { + Client.Any2EVMMessage memory message = + _convertToGeneralMessage(_generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1)); + + vm.expectRevert(); + + s_offRamp.ccipReceive(message); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.commit.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.commit.t.sol new file mode 100644 index 00000000000..a942b98cc1e --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.commit.t.sol @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IFeeQuoter} from "../../../interfaces/IFeeQuoter.sol"; +import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; + +import {FeeQuoter} from "../../../FeeQuoter.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +contract OffRamp_commit is OffRampSetup { + uint64 internal s_maxInterval = 12; + + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + + s_latestSequenceNumber = uint64(uint256(s_configDigestCommit)); + } + + function test_ReportAndPriceUpdate_Success() public { + OffRamp.CommitReport memory commitReport = _constructCommitReport(); + + vm.expectEmit(); + emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + + assertEq(s_maxInterval + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); + assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); + } + + function test_ReportOnlyRootSuccess_gas() public { + uint64 max1 = 931; + bytes32 root = "Only a single root"; + + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: max1, + merkleRoot: root + }); + + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + vm.expectEmit(); + emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + + assertEq(max1 + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); + assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); + assertEq(block.timestamp, s_offRamp.getMerkleRoot(SOURCE_CHAIN_SELECTOR_1, root)); + } + + function test_RootWithRMNDisabled_success() public { + // force RMN verification to fail + vm.mockCallRevert(address(s_mockRMNRemote), abi.encodeWithSelector(IRMNRemote.verify.selector), bytes("")); + + // but ☝️ doesn't matter because RMN verification is disabled + OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); + dynamicConfig.isRMNVerificationDisabled = true; + s_offRamp.setDynamicConfig(dynamicConfig); + + uint64 max1 = 931; + bytes32 root = "Only a single root"; + + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: max1, + merkleRoot: root + }); + + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + vm.expectEmit(); + emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + + assertEq(max1 + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); + assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); + assertEq(block.timestamp, s_offRamp.getMerkleRoot(SOURCE_CHAIN_SELECTOR_1, root)); + } + + function test_StaleReportWithRoot_Success() public { + uint64 maxSeq = 12; + uint224 tokenStartPrice = IFeeQuoter(s_offRamp.getDynamicConfig().feeQuoter).getTokenPrice(s_sourceFeeToken).value; + + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: maxSeq, + merkleRoot: "stale report 1" + }); + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + vm.expectEmit(); + emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + + assertEq(maxSeq + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); + assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); + + commitReport.merkleRoots[0].minSeqNr = maxSeq + 1; + commitReport.merkleRoots[0].maxSeqNr = maxSeq * 2; + commitReport.merkleRoots[0].merkleRoot = "stale report 2"; + + vm.expectEmit(); + emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + + assertEq(maxSeq * 2 + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); + assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); + assertEq(tokenStartPrice, IFeeQuoter(s_offRamp.getDynamicConfig().feeQuoter).getTokenPrice(s_sourceFeeToken).value); + } + + function test_OnlyTokenPriceUpdates_Success() public { + // force RMN verification to fail + vm.mockCallRevert(address(s_mockRMNRemote), abi.encodeWithSelector(IRMNRemote.verify.selector), bytes("")); + + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); + OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + + assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); + } + + function test_OnlyGasPriceUpdates_Success() public { + // force RMN verification to fail + vm.mockCallRevert(address(s_mockRMNRemote), abi.encodeWithSelector(IRMNRemote.verify.selector), bytes("")); + + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); + OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); + } + + function test_PriceSequenceNumberCleared_Success() public { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); + OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); + _commit(commitReport, s_latestSequenceNumber); + + assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); + + vm.startPrank(OWNER); + MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: uint8(Internal.OCRPluginType.Execution), + configDigest: s_configDigestExec, + F: F, + isSignatureVerificationEnabled: false, + signers: s_emptySigners, + transmitters: s_validTransmitters + }); + s_offRamp.setOCR3Configs(ocrConfigs); + + // Execution plugin OCR config should not clear latest epoch and round + assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); + + // Commit plugin config should clear latest epoch & round + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: uint8(Internal.OCRPluginType.Commit), + configDigest: s_configDigestCommit, + F: F, + isSignatureVerificationEnabled: true, + signers: s_validSigners, + transmitters: s_validTransmitters + }); + s_offRamp.setOCR3Configs(ocrConfigs); + + assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); + + // The same sequence number can be reported again + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); + + _commit(commitReport, s_latestSequenceNumber); + } + + function test_ValidPriceUpdateThenStaleReportWithRoot_Success() public { + uint64 maxSeq = 12; + uint224 tokenPrice1 = 4e18; + uint224 tokenPrice2 = 5e18; + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); + OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, tokenPrice1), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, tokenPrice1, block.timestamp); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); + + roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: maxSeq, + merkleRoot: "stale report" + }); + commitReport.priceUpdates = _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, tokenPrice2); + commitReport.merkleRoots = roots; + + vm.expectEmit(); + emit OffRamp.CommitReportAccepted(commitReport.merkleRoots, commitReport.priceUpdates); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted(uint8(Internal.OCRPluginType.Commit), s_configDigestCommit, s_latestSequenceNumber); + + _commit(commitReport, s_latestSequenceNumber); + + assertEq(maxSeq + 1, s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR).minSeqNr); + assertEq(tokenPrice1, IFeeQuoter(s_offRamp.getDynamicConfig().feeQuoter).getTokenPrice(s_sourceFeeToken).value); + assertEq(s_latestSequenceNumber, s_offRamp.getLatestPriceSequenceNumber()); + } + + // Reverts + + function test_UnauthorizedTransmitter_Revert() public { + OffRamp.CommitReport memory commitReport = _constructCommitReport(); + + bytes32[3] memory reportContext = + [s_configDigestCommit, bytes32(uint256(s_latestSequenceNumber)), s_configDigestCommit]; + + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, abi.encode(commitReport), reportContext, F + 1); + + vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); + s_offRamp.commit(reportContext, abi.encode(commitReport), rs, ss, rawVs); + } + + function test_NoConfig_Revert() public { + _redeployOffRampWithNoOCRConfigs(); + + OffRamp.CommitReport memory commitReport = _constructCommitReport(); + + bytes32[3] memory reportContext = [bytes32(""), s_configDigestCommit, s_configDigestCommit]; + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, abi.encode(commitReport), reportContext, F + 1); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(); + s_offRamp.commit(reportContext, abi.encode(commitReport), rs, ss, rawVs); + } + + function test_NoConfigWithOtherConfigPresent_Revert() public { + _redeployOffRampWithNoOCRConfigs(); + + MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: uint8(Internal.OCRPluginType.Execution), + configDigest: s_configDigestExec, + F: F, + isSignatureVerificationEnabled: false, + signers: s_emptySigners, + transmitters: s_validTransmitters + }); + s_offRamp.setOCR3Configs(ocrConfigs); + + OffRamp.CommitReport memory commitReport = _constructCommitReport(); + + bytes32[3] memory reportContext = [bytes32(""), s_configDigestCommit, s_configDigestCommit]; + (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = + _getSignaturesForDigest(s_validSignerKeys, abi.encode(commitReport), reportContext, F + 1); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(); + s_offRamp.commit(reportContext, abi.encode(commitReport), rs, ss, rawVs); + } + + function test_FailedRMNVerification_Reverts() public { + // force RMN verification to fail + vm.mockCallRevert(address(s_mockRMNRemote), abi.encodeWithSelector(IRMNRemote.verify.selector), bytes("")); + + OffRamp.CommitReport memory commitReport = _constructCommitReport(); + vm.expectRevert(); + _commit(commitReport, s_latestSequenceNumber); + } + + function test_Unhealthy_Revert() public { + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + minSeqNr: 1, + maxSeqNr: 2, + merkleRoot: "Only a single root", + onRampAddress: abi.encode(ON_RAMP_ADDRESS_1) + }); + + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.CursedByRMN.selector, roots[0].sourceChainSelector)); + _commit(commitReport, s_latestSequenceNumber); + } + + function test_InvalidRootRevert() public { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: 4, + merkleRoot: bytes32(0) + }); + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + vm.expectRevert(OffRamp.InvalidRoot.selector); + _commit(commitReport, s_latestSequenceNumber); + } + + function test_InvalidInterval_Revert() public { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 2, + maxSeqNr: 2, + merkleRoot: bytes32(0) + }); + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + vm.expectRevert( + abi.encodeWithSelector( + OffRamp.InvalidInterval.selector, roots[0].sourceChainSelector, roots[0].minSeqNr, roots[0].maxSeqNr + ) + ); + _commit(commitReport, s_latestSequenceNumber); + } + + function test_InvalidIntervalMinLargerThanMax_Revert() public { + s_offRamp.getSourceChainConfig(SOURCE_CHAIN_SELECTOR); + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: 0, + merkleRoot: bytes32(0) + }); + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + vm.expectRevert( + abi.encodeWithSelector( + OffRamp.InvalidInterval.selector, roots[0].sourceChainSelector, roots[0].minSeqNr, roots[0].maxSeqNr + ) + ); + _commit(commitReport, s_latestSequenceNumber); + } + + function test_ZeroEpochAndRound_Revert() public { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); + OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }); + + vm.expectRevert(OffRamp.StaleCommitReport.selector); + _commit(commitReport, 0); + } + + function test_OnlyPriceUpdateStaleReport_Revert() public { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](0); + OffRamp.CommitReport memory commitReport = OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }); + + vm.expectEmit(); + emit FeeQuoter.UsdPerTokenUpdated(s_sourceFeeToken, 4e18, block.timestamp); + _commit(commitReport, s_latestSequenceNumber); + + vm.expectRevert(OffRamp.StaleCommitReport.selector); + _commit(commitReport, s_latestSequenceNumber); + } + + function test_SourceChainNotEnabled_Revert() public { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: 0, + onRampAddress: abi.encode(ON_RAMP_ADDRESS_1), + minSeqNr: 1, + maxSeqNr: 2, + merkleRoot: "Only a single root" + }); + + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.SourceChainNotEnabled.selector, 0)); + _commit(commitReport, s_latestSequenceNumber); + } + + function test_RootAlreadyCommitted_Revert() public { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: 2, + merkleRoot: "Only a single root" + }); + OffRamp.CommitReport memory commitReport = + OffRamp.CommitReport({priceUpdates: _getEmptyPriceUpdates(), merkleRoots: roots, rmnSignatures: s_rmnSignatures}); + + _commit(commitReport, s_latestSequenceNumber); + commitReport.merkleRoots[0].minSeqNr = 3; + commitReport.merkleRoots[0].maxSeqNr = 3; + + vm.expectRevert( + abi.encodeWithSelector(OffRamp.RootAlreadyCommitted.selector, roots[0].sourceChainSelector, roots[0].merkleRoot) + ); + _commit(commitReport, ++s_latestSequenceNumber); + } + + function test_CommitOnRampMismatch_Revert() public { + OffRamp.CommitReport memory commitReport = _constructCommitReport(); + + commitReport.merkleRoots[0].onRampAddress = ON_RAMP_ADDRESS_2; + + vm.expectRevert(abi.encodeWithSelector(OffRamp.CommitOnRampMismatch.selector, ON_RAMP_ADDRESS_2, ON_RAMP_ADDRESS_1)); + _commit(commitReport, s_latestSequenceNumber); + } + + function _constructCommitReport() internal view returns (OffRamp.CommitReport memory) { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: ON_RAMP_ADDRESS_1, + minSeqNr: 1, + maxSeqNr: s_maxInterval, + merkleRoot: "test #2" + }); + + return OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol new file mode 100644 index 00000000000..da23daac0ed --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.constructor.t.sol @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; + +import {Internal} from "../../../libraries/Internal.sol"; +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {OffRampHelper} from "../../helpers/OffRampHelper.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +contract OffRamp_constructor is OffRampSetup { + function test_Constructor_Success() public { + OffRamp.StaticConfig memory staticConfig = OffRamp.StaticConfig({ + chainSelector: DEST_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + tokenAdminRegistry: address(s_tokenAdminRegistry), + nonceManager: address(s_inboundNonceManager) + }); + OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](2); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + sourceChainConfigs[1] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1 + 1, + onRamp: ON_RAMP_ADDRESS_2, + isEnabled: true + }); + + OffRamp.SourceChainConfig memory expectedSourceChainConfig1 = OffRamp.SourceChainConfig({ + router: s_destRouter, + isEnabled: true, + minSeqNr: 1, + onRamp: sourceChainConfigs[0].onRamp + }); + + OffRamp.SourceChainConfig memory expectedSourceChainConfig2 = OffRamp.SourceChainConfig({ + router: s_destRouter, + isEnabled: true, + minSeqNr: 1, + onRamp: sourceChainConfigs[1].onRamp + }); + + uint64[] memory expectedSourceChainSelectors = new uint64[](2); + expectedSourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; + expectedSourceChainSelectors[1] = SOURCE_CHAIN_SELECTOR_1 + 1; + + vm.expectEmit(); + emit OffRamp.StaticConfigSet(staticConfig); + + vm.expectEmit(); + emit OffRamp.DynamicConfigSet(dynamicConfig); + + vm.expectEmit(); + emit OffRamp.SourceChainSelectorAdded(SOURCE_CHAIN_SELECTOR_1); + + vm.expectEmit(); + emit OffRamp.SourceChainConfigSet(SOURCE_CHAIN_SELECTOR_1, expectedSourceChainConfig1); + + vm.expectEmit(); + emit OffRamp.SourceChainSelectorAdded(SOURCE_CHAIN_SELECTOR_1 + 1); + + vm.expectEmit(); + emit OffRamp.SourceChainConfigSet(SOURCE_CHAIN_SELECTOR_1 + 1, expectedSourceChainConfig2); + + s_offRamp = new OffRampHelper(staticConfig, dynamicConfig, sourceChainConfigs); + + MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: uint8(Internal.OCRPluginType.Execution), + configDigest: s_configDigestExec, + F: F, + isSignatureVerificationEnabled: false, + signers: s_emptySigners, + transmitters: s_validTransmitters + }); + + s_offRamp.setOCR3Configs(ocrConfigs); + + // Static config + OffRamp.StaticConfig memory gotStaticConfig = s_offRamp.getStaticConfig(); + assertEq(staticConfig.chainSelector, gotStaticConfig.chainSelector); + assertEq(address(staticConfig.rmnRemote), address(gotStaticConfig.rmnRemote)); + assertEq(staticConfig.tokenAdminRegistry, gotStaticConfig.tokenAdminRegistry); + + // Dynamic config + OffRamp.DynamicConfig memory gotDynamicConfig = s_offRamp.getDynamicConfig(); + _assertSameConfig(dynamicConfig, gotDynamicConfig); + + // OCR Config + MultiOCR3Base.OCRConfig memory expectedOCRConfig = MultiOCR3Base.OCRConfig({ + configInfo: MultiOCR3Base.ConfigInfo({ + configDigest: ocrConfigs[0].configDigest, + F: ocrConfigs[0].F, + n: 0, + isSignatureVerificationEnabled: ocrConfigs[0].isSignatureVerificationEnabled + }), + signers: s_emptySigners, + transmitters: s_validTransmitters + }); + MultiOCR3Base.OCRConfig memory gotOCRConfig = s_offRamp.latestConfigDetails(uint8(Internal.OCRPluginType.Execution)); + _assertOCRConfigEquality(expectedOCRConfig, gotOCRConfig); + + (uint64[] memory actualSourceChainSelectors, OffRamp.SourceChainConfig[] memory actualSourceChainConfigs) = + s_offRamp.getAllSourceChainConfigs(); + + _assertSourceChainConfigEquality(actualSourceChainConfigs[0], expectedSourceChainConfig1); + _assertSourceChainConfigEquality(actualSourceChainConfigs[1], expectedSourceChainConfig2); + + // OffRamp initial values + assertEq("OffRamp 1.6.0-dev", s_offRamp.typeAndVersion()); + assertEq(OWNER, s_offRamp.owner()); + assertEq(0, s_offRamp.getLatestPriceSequenceNumber()); + + // assertion for source chain selector + for (uint256 i = 0; i < expectedSourceChainSelectors.length; i++) { + assertEq(expectedSourceChainSelectors[i], actualSourceChainSelectors[i]); + } + } + + // Revert + function test_ZeroOnRampAddress_Revert() public { + uint64[] memory sourceChainSelectors = new uint64[](1); + sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRamp: new bytes(0), + isEnabled: true + }); + + vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); + + s_offRamp = new OffRampHelper( + OffRamp.StaticConfig({ + chainSelector: DEST_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + tokenAdminRegistry: address(s_tokenAdminRegistry), + nonceManager: address(s_inboundNonceManager) + }), + _generateDynamicOffRampConfig(address(s_feeQuoter)), + sourceChainConfigs + ); + } + + function test_SourceChainSelector_Revert() public { + uint64[] memory sourceChainSelectors = new uint64[](1); + sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](1); + sourceChainConfigs[0] = OffRamp.SourceChainConfigArgs({ + router: s_destRouter, + sourceChainSelector: 0, + onRamp: ON_RAMP_ADDRESS_1, + isEnabled: true + }); + + vm.expectRevert(OffRamp.ZeroChainSelectorNotAllowed.selector); + + s_offRamp = new OffRampHelper( + OffRamp.StaticConfig({ + chainSelector: DEST_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + tokenAdminRegistry: address(s_tokenAdminRegistry), + nonceManager: address(s_inboundNonceManager) + }), + _generateDynamicOffRampConfig(address(s_feeQuoter)), + sourceChainConfigs + ); + } + + function test_ZeroRMNRemote_Revert() public { + uint64[] memory sourceChainSelectors = new uint64[](1); + sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); + + vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); + + s_offRamp = new OffRampHelper( + OffRamp.StaticConfig({ + chainSelector: DEST_CHAIN_SELECTOR, + rmnRemote: IRMNRemote(ZERO_ADDRESS), + tokenAdminRegistry: address(s_tokenAdminRegistry), + nonceManager: address(s_inboundNonceManager) + }), + _generateDynamicOffRampConfig(address(s_feeQuoter)), + sourceChainConfigs + ); + } + + function test_ZeroChainSelector_Revert() public { + uint64[] memory sourceChainSelectors = new uint64[](1); + sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); + + vm.expectRevert(OffRamp.ZeroChainSelectorNotAllowed.selector); + + s_offRamp = new OffRampHelper( + OffRamp.StaticConfig({ + chainSelector: 0, + rmnRemote: s_mockRMNRemote, + tokenAdminRegistry: address(s_tokenAdminRegistry), + nonceManager: address(s_inboundNonceManager) + }), + _generateDynamicOffRampConfig(address(s_feeQuoter)), + sourceChainConfigs + ); + } + + function test_ZeroTokenAdminRegistry_Revert() public { + uint64[] memory sourceChainSelectors = new uint64[](1); + sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); + + vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); + + s_offRamp = new OffRampHelper( + OffRamp.StaticConfig({ + chainSelector: DEST_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + tokenAdminRegistry: ZERO_ADDRESS, + nonceManager: address(s_inboundNonceManager) + }), + _generateDynamicOffRampConfig(address(s_feeQuoter)), + sourceChainConfigs + ); + } + + function test_ZeroNonceManager_Revert() public { + uint64[] memory sourceChainSelectors = new uint64[](1); + sourceChainSelectors[0] = SOURCE_CHAIN_SELECTOR_1; + + OffRamp.SourceChainConfigArgs[] memory sourceChainConfigs = new OffRamp.SourceChainConfigArgs[](0); + + vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); + + s_offRamp = new OffRampHelper( + OffRamp.StaticConfig({ + chainSelector: DEST_CHAIN_SELECTOR, + rmnRemote: s_mockRMNRemote, + tokenAdminRegistry: address(s_tokenAdminRegistry), + nonceManager: ZERO_ADDRESS + }), + _generateDynamicOffRampConfig(address(s_feeQuoter)), + sourceChainConfigs + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol new file mode 100644 index 00000000000..b1c33efb106 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.execute.t.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IMessageInterceptor} from "../../../interfaces/IMessageInterceptor.sol"; + +import {Internal} from "../../../libraries/Internal.sol"; +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +import {Vm} from "forge-std/Vm.sol"; + +contract OffRamp_execute is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); + } + + // Asserts that execute completes + function test_SingleReport_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted( + uint8(Internal.OCRPluginType.Execution), s_configDigestExec, uint64(uint256(s_configDigestExec)) + ); + + vm.recordLogs(); + + _execute(reports); + + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_MultipleReports_Success() public { + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); + + messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); + messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages2); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted( + uint8(Internal.OCRPluginType.Execution), s_configDigestExec, uint64(uint256(s_configDigestExec)) + ); + + vm.recordLogs(); + _execute(reports); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + assertExecutionStateChangedEventLogs( + logs, + messages1[0].header.sourceChainSelector, + messages1[0].header.sequenceNumber, + messages1[0].header.messageId, + _hashMessage(messages1[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + messages1[1].header.sourceChainSelector, + messages1[1].header.sequenceNumber, + messages1[1].header.messageId, + _hashMessage(messages1[1], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + messages2[0].header.sourceChainSelector, + messages2[0].header.sequenceNumber, + messages2[0].header.messageId, + _hashMessage(messages2[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_LargeBatch_Success() public { + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](10); + for (uint64 i = 0; i < reports.length; ++i) { + Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](3); + messages[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1 + i * 3); + messages[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2 + i * 3); + messages[2] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3 + i * 3); + + reports[i] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + } + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted( + uint8(Internal.OCRPluginType.Execution), s_configDigestExec, uint64(uint256(s_configDigestExec)) + ); + + vm.recordLogs(); + _execute(reports); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + for (uint64 i = 0; i < reports.length; ++i) { + for (uint64 j = 0; j < reports[i].messages.length; ++j) { + assertExecutionStateChangedEventLogs( + logs, + reports[i].messages[j].header.sourceChainSelector, + reports[i].messages[j].header.sequenceNumber, + reports[i].messages[j].header.messageId, + _hashMessage(reports[i].messages[j], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + } + } + + function test_MultipleReportsWithPartialValidationFailures_Success() public { + _enableInboundMessageInterceptor(); + + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); + + messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); + messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 3); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages2); + + s_inboundMessageInterceptor.setMessageIdValidationState(messages1[0].header.messageId, true); + s_inboundMessageInterceptor.setMessageIdValidationState(messages2[0].header.messageId, true); + + vm.expectEmit(); + emit MultiOCR3Base.Transmitted( + uint8(Internal.OCRPluginType.Execution), s_configDigestExec, uint64(uint256(s_configDigestExec)) + ); + + vm.recordLogs(); + _execute(reports); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + assertExecutionStateChangedEventLogs( + logs, + messages1[0].header.sourceChainSelector, + messages1[0].header.sequenceNumber, + messages1[0].header.messageId, + _hashMessage(messages1[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector( + IMessageInterceptor.MessageValidationError.selector, + abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) + ) + ); + + assertExecutionStateChangedEventLogs( + logs, + messages1[1].header.sourceChainSelector, + messages1[1].header.sequenceNumber, + messages1[1].header.messageId, + _hashMessage(messages1[1], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + messages2[0].header.sourceChainSelector, + messages2[0].header.sequenceNumber, + messages2[0].header.messageId, + _hashMessage(messages2[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector( + IMessageInterceptor.MessageValidationError.selector, + abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) + ) + ); + } + + // Reverts + + function test_UnauthorizedTransmitter_Revert() public { + bytes32[3] memory reportContext = [s_configDigestExec, s_configDigestExec, s_configDigestExec]; + + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); + s_offRamp.execute(reportContext, abi.encode(reports)); + } + + function test_NoConfig_Revert() public { + _redeployOffRampWithNoOCRConfigs(); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); + + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + bytes32[3] memory reportContext = [bytes32(""), s_configDigestExec, s_configDigestExec]; + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); + s_offRamp.execute(reportContext, abi.encode(reports)); + } + + function test_NoConfigWithOtherConfigPresent_Revert() public { + _redeployOffRampWithNoOCRConfigs(); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); + + MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: uint8(Internal.OCRPluginType.Commit), + configDigest: s_configDigestCommit, + F: F, + isSignatureVerificationEnabled: true, + signers: s_validSigners, + transmitters: s_validTransmitters + }); + s_offRamp.setOCR3Configs(ocrConfigs); + + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + bytes32[3] memory reportContext = [bytes32(""), s_configDigestExec, s_configDigestExec]; + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(MultiOCR3Base.UnauthorizedTransmitter.selector); + s_offRamp.execute(reportContext, abi.encode(reports)); + } + + function test_WrongConfigWithSigners_Revert() public { + _redeployOffRampWithNoOCRConfigs(); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); + + s_configDigestExec = _getBasicConfigDigest(1, s_validSigners, s_validTransmitters); + + MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](1); + ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ + ocrPluginType: uint8(Internal.OCRPluginType.Execution), + configDigest: s_configDigestExec, + F: F, + isSignatureVerificationEnabled: true, + signers: s_validSigners, + transmitters: s_validTransmitters + }); + + vm.expectRevert(OffRamp.SignatureVerificationNotAllowedInExecutionPlugin.selector); + s_offRamp.setOCR3Configs(ocrConfigs); + } + + function test_ZeroReports_Revert() public { + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](0); + + vm.expectRevert(OffRamp.EmptyBatch.selector); + _execute(reports); + } + + function test_IncorrectArrayType_Revert() public { + bytes32[3] memory reportContext = [s_configDigestExec, s_configDigestExec, s_configDigestExec]; + + uint256[] memory wrongData = new uint256[](2); + wrongData[0] = 1; + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(); + s_offRamp.execute(reportContext, abi.encode(wrongData)); + } + + function test_NonArray_Revert() public { + bytes32[3] memory reportContext = [s_configDigestExec, s_configDigestExec, s_configDigestExec]; + + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + vm.startPrank(s_validTransmitters[0]); + vm.expectRevert(); + s_offRamp.execute(reportContext, abi.encode(report)); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleMessage.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleMessage.t.sol new file mode 100644 index 00000000000..45fa18930d9 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleMessage.t.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {IMessageInterceptor} from "../../../interfaces/IMessageInterceptor.sol"; +import {IRouter} from "../../../interfaces/IRouter.sol"; + +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {MaybeRevertMessageReceiverNo165} from "../../helpers/receivers/MaybeRevertMessageReceiverNo165.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +contract OffRamp_executeSingleMessage is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + vm.startPrank(address(s_offRamp)); + } + + function test_executeSingleMessage_NoTokens_Success() public { + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + + Client.Any2EVMMessage memory expectedAny2EvmMessage = Client.Any2EVMMessage({ + messageId: message.header.messageId, + sourceChainSelector: message.header.sourceChainSelector, + sender: message.sender, + data: message.data, + destTokenAmounts: new Client.EVMTokenAmount[](0) + }); + vm.expectCall( + address(s_destRouter), + abi.encodeWithSelector( + IRouter.routeMessage.selector, + expectedAny2EvmMessage, + Internal.GAS_FOR_CALL_EXACT_CHECK, + message.gasLimit, + message.receiver + ) + ); + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } + + function test_executeSingleMessage_WithTokens_Success() public { + Internal.Any2EVMRampMessage memory message = + _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1)[0]; + bytes[] memory offchainTokenData = new bytes[](message.tokenAmounts.length); + + vm.expectCall( + s_destPoolByToken[s_destTokens[0]], + abi.encodeWithSelector( + LockReleaseTokenPool.releaseOrMint.selector, + Pool.ReleaseOrMintInV1({ + originalSender: message.sender, + receiver: message.receiver, + amount: message.tokenAmounts[0].amount, + localToken: message.tokenAmounts[0].destTokenAddress, + remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, + sourcePoolAddress: message.tokenAmounts[0].sourcePoolAddress, + sourcePoolData: message.tokenAmounts[0].extraData, + offchainTokenData: offchainTokenData[0] + }) + ) + ); + + s_offRamp.executeSingleMessage(message, offchainTokenData, new uint32[](0)); + } + + function test_executeSingleMessage_WithVInterception_Success() public { + vm.stopPrank(); + vm.startPrank(OWNER); + _enableInboundMessageInterceptor(); + vm.startPrank(address(s_offRamp)); + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } + + function test_NonContract_Success() public { + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + message.receiver = STRANGER; + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } + + function test_NonContractWithTokens_Success() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 50; + vm.expectEmit(); + emit TokenPool.Released(address(s_offRamp), STRANGER, amounts[0]); + vm.expectEmit(); + emit TokenPool.Minted(address(s_offRamp), STRANGER, amounts[1]); + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); + message.receiver = STRANGER; + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } + + // Reverts + + function test_TokenHandlingError_Revert() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 50; + + bytes memory errorMessage = "Random token pool issue"; + + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); + s_maybeRevertingPool.setShouldRevert(errorMessage); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, errorMessage)); + + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } + + function test_ZeroGasDONExecution_Revert() public { + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + message.gasLimit = 0; + + vm.expectRevert(abi.encodeWithSelector(OffRamp.ReceiverError.selector, "")); + + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } + + function test_MessageSender_Revert() public { + vm.stopPrank(); + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + vm.expectRevert(OffRamp.CanOnlySelfCall.selector); + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } + + function test_executeSingleMessage_WithFailingValidation_Revert() public { + vm.stopPrank(); + vm.startPrank(OWNER); + _enableInboundMessageInterceptor(); + vm.startPrank(address(s_offRamp)); + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + s_inboundMessageInterceptor.setMessageIdValidationState(message.header.messageId, true); + vm.expectRevert( + abi.encodeWithSelector( + IMessageInterceptor.MessageValidationError.selector, + abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) + ) + ); + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } + + function test_executeSingleMessage_WithFailingValidationNoRouterCall_Revert() public { + vm.stopPrank(); + vm.startPrank(OWNER); + _enableInboundMessageInterceptor(); + vm.startPrank(address(s_offRamp)); + + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + + // Setup the receiver to a non-CCIP Receiver, which will skip the Router call (but should still perform the validation) + MaybeRevertMessageReceiverNo165 newReceiver = new MaybeRevertMessageReceiverNo165(true); + message.receiver = address(newReceiver); + message.header.messageId = _hashMessage(message, ON_RAMP_ADDRESS_1); + + s_inboundMessageInterceptor.setMessageIdValidationState(message.header.messageId, true); + vm.expectRevert( + abi.encodeWithSelector( + IMessageInterceptor.MessageValidationError.selector, + abi.encodeWithSelector(IMessageInterceptor.MessageValidationError.selector, bytes("Invalid message")) + ) + ); + s_offRamp.executeSingleMessage(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol new file mode 100644 index 00000000000..aa5b2e93d5a --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.executeSingleReport.t.sol @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CallWithExactGas} from "../../../../shared/call/CallWithExactGas.sol"; +import {NonceManager} from "../../../NonceManager.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {ConformingReceiver} from "../../helpers/receivers/ConformingReceiver.sol"; +import {MaybeRevertMessageReceiver} from "../../helpers/receivers/MaybeRevertMessageReceiver.sol"; +import {MaybeRevertMessageReceiverNo165} from "../../helpers/receivers/MaybeRevertMessageReceiverNo165.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +import {Vm} from "forge-std/Vm.sol"; + +contract OffRamp_executeSingleReport is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_3, 1); + } + + function test_SingleMessageNoTokens_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + messages[0].header.nonce++; + messages[0].header.sequenceNumber++; + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + assertGt(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender), nonceBefore); + } + + function test_SingleMessageNoTokensUnordered_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + messages[0].header.nonce = 0; + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + // Nonce never increments on unordered messages. + uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertEq( + s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender), + nonceBefore, + "nonce must remain unchanged on unordered messages" + ); + + messages[0].header.sequenceNumber++; + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + // Nonce never increments on unordered messages. + nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender); + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + assertEq( + s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender), + nonceBefore, + "nonce must remain unchanged on unordered messages" + ); + } + + function test_SingleMessageNoTokensOtherChain_Success() public { + Internal.Any2EVMRampMessage[] memory messagesChain1 = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messagesChain1), new OffRamp.GasLimitOverride[](0) + ); + + uint64 nonceChain1 = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messagesChain1[0].sender); + assertGt(nonceChain1, 0); + + Internal.Any2EVMRampMessage[] memory messagesChain2 = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3); + assertEq(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, messagesChain2[0].sender), 0); + + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messagesChain2), new OffRamp.GasLimitOverride[](0) + ); + assertGt(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_3, messagesChain2[0].sender), 0); + + // Other chain's nonce is unaffected + assertEq(s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messagesChain1[0].sender), nonceChain1); + } + + function test_ReceiverError_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + bytes memory realError1 = new bytes(2); + realError1[0] = 0xbe; + realError1[1] = 0xef; + s_reverting_receiver.setErr(realError1); + + messages[0].receiver = address(s_reverting_receiver); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + // Nonce should increment on non-strict + assertEq(uint64(0), s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER))); + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector( + OffRamp.ReceiverError.selector, + abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, realError1) + ) + ); + assertEq(uint64(1), s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER))); + } + + function test_SkippedIncorrectNonce_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + messages[0].header.nonce++; + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + vm.expectEmit(); + emit NonceManager.SkippedIncorrectNonce( + messages[0].header.sourceChainSelector, messages[0].header.nonce, messages[0].sender + ); + + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + } + + function test_SkippedIncorrectNonceStillExecutes_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + messages[1].header.nonce++; + messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); + + vm.expectEmit(); + emit NonceManager.SkippedIncorrectNonce(SOURCE_CHAIN_SELECTOR_1, messages[1].header.nonce, messages[1].sender); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test__execute_SkippedAlreadyExecutedMessage_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + vm.expectEmit(); + emit OffRamp.SkippedAlreadyExecutedMessage(SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber); + + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + } + + function test__execute_SkippedAlreadyExecutedMessageUnordered_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + messages[0].header.nonce = 0; + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + vm.expectEmit(); + emit OffRamp.SkippedAlreadyExecutedMessage(SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber); + + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + } + + // Send a message to a contract that does not implement the CCIPReceiver interface + // This should execute successfully. + function test_SingleMessageToNonCCIPReceiver_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + MaybeRevertMessageReceiverNo165 newReceiver = new MaybeRevertMessageReceiverNo165(true); + messages[0].receiver = address(newReceiver); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_SingleMessagesNoTokensSuccess_gas() public { + vm.pauseGasMetering(); + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + vm.resumeGasMetering(); + vm.recordLogs(); + s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_TwoMessagesWithTokensSuccess_gas() public { + vm.pauseGasMetering(); + Internal.Any2EVMRampMessage[] memory messages = + _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + // Set message 1 to use another receiver to simulate more fair gas costs + messages[1].receiver = address(s_secondary_receiver); + messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); + + Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + vm.resumeGasMetering(); + vm.recordLogs(); + s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages[1].header.sequenceNumber, + messages[1].header.messageId, + _hashMessage(messages[1], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_TwoMessagesWithTokensAndGE_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + // Set message 1 to use another receiver to simulate more fair gas costs + messages[1].receiver = address(s_secondary_receiver); + messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); + + assertEq(uint64(0), s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER))); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), _getGasLimitsFromMessages(messages) + ); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages[1].header.sequenceNumber, + messages[1].header.messageId, + _hashMessage(messages[1], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + assertEq(uint64(2), s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER))); + } + + function test_Fuzz_InterleavingOrderedAndUnorderedMessages_Success( + bool[7] memory orderings + ) public { + Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](orderings.length); + // number of tokens needs to be capped otherwise we hit UnsupportedNumberOfTokens. + Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](3); + for (uint256 i = 0; i < 3; ++i) { + tokenAmounts[i].token = s_sourceTokens[i % s_sourceTokens.length]; + tokenAmounts[i].amount = 1e18; + } + uint64 expectedNonce = 0; + + for (uint256 i = 0; i < orderings.length; ++i) { + messages[i] = + _generateAny2EVMMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, uint64(i + 1), tokenAmounts, !orderings[i]); + if (orderings[i]) { + messages[i].header.nonce = ++expectedNonce; + } + messages[i].header.messageId = _hashMessage(messages[i], ON_RAMP_ADDRESS_1); + } + + uint64 nonceBefore = s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER)); + assertEq(uint64(0), nonceBefore, "nonce before exec should be 0"); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), _getGasLimitsFromMessages(messages) + ); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + // all executions should succeed. + for (uint256 i = 0; i < orderings.length; ++i) { + assertEq( + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, messages[i].header.sequenceNumber)), + uint256(Internal.MessageExecutionState.SUCCESS) + ); + + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages[i].header.sequenceNumber, + messages[i].header.messageId, + _hashMessage(messages[i], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + assertEq( + nonceBefore + expectedNonce, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, abi.encode(OWNER)) + ); + } + + function test_InvalidSourcePoolAddress_Success() public { + address fakePoolAddress = address(0x0000000000333333); + + Internal.Any2EVMRampMessage[] memory messages = + _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + messages[0].tokenAmounts[0].sourcePoolAddress = abi.encode(fakePoolAddress); + + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector( + OffRamp.TokenHandlingError.selector, + abi.encodeWithSelector(TokenPool.InvalidSourcePoolAddress.selector, abi.encode(fakePoolAddress)) + ) + ); + } + + function test_WithCurseOnAnotherSourceChain_Success() public { + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_2, true); + s_offRamp.executeSingleReport( + _generateReportFromMessages( + SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) + ), + new OffRamp.GasLimitOverride[](0) + ); + } + + function test_Unhealthy_Success() public { + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); + + vm.expectEmit(); + emit OffRamp.SkippedReportExecution(SOURCE_CHAIN_SELECTOR_1); + s_offRamp.executeSingleReport( + _generateReportFromMessages( + SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) + ), + new OffRamp.GasLimitOverride[](0) + ); + + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, false); + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages( + SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) + ), + new OffRamp.GasLimitOverride[](0) + ); + + _assertNoEmit(OffRamp.SkippedReportExecution.selector); + } + + // Reverts + + function test_MismatchingDestChainSelector_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3); + messages[0].header.destChainSelector = DEST_CHAIN_SELECTOR + 1; + + Internal.ExecutionReport memory executionReport = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + vm.expectRevert( + abi.encodeWithSelector(OffRamp.InvalidMessageDestChainSelector.selector, messages[0].header.destChainSelector) + ); + s_offRamp.executeSingleReport(executionReport, new OffRamp.GasLimitOverride[](0)); + } + + function test_UnhealthySingleChainCurse_Revert() public { + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, true); + vm.expectEmit(); + emit OffRamp.SkippedReportExecution(SOURCE_CHAIN_SELECTOR_1); + s_offRamp.executeSingleReport( + _generateReportFromMessages( + SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) + ), + new OffRamp.GasLimitOverride[](0) + ); + vm.recordLogs(); + // Uncurse should succeed + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_1, false); + s_offRamp.executeSingleReport( + _generateReportFromMessages( + SOURCE_CHAIN_SELECTOR_1, _generateMessagesWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) + ), + new OffRamp.GasLimitOverride[](0) + ); + _assertNoEmit(OffRamp.SkippedReportExecution.selector); + } + + function test_UnexpectedTokenData_Revert() public { + Internal.ExecutionReport memory report = _generateReportFromMessages( + SOURCE_CHAIN_SELECTOR_1, _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1) + ); + report.offchainTokenData = new bytes[][](report.messages.length + 1); + + vm.expectRevert(OffRamp.UnexpectedTokenData.selector); + + s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); + } + + function test_EmptyReport_Revert() public { + vm.expectRevert(abi.encodeWithSelector(OffRamp.EmptyReport.selector, SOURCE_CHAIN_SELECTOR_1)); + + s_offRamp.executeSingleReport( + Internal.ExecutionReport({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + proofs: new bytes32[](0), + proofFlagBits: 0, + messages: new Internal.Any2EVMRampMessage[](0), + offchainTokenData: new bytes[][](0) + }), + new OffRamp.GasLimitOverride[](0) + ); + } + + function test_RootNotCommitted_Revert() public { + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 0); + vm.expectRevert(abi.encodeWithSelector(OffRamp.RootNotCommitted.selector, SOURCE_CHAIN_SELECTOR_1)); + + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), _getGasLimitsFromMessages(messages) + ); + } + + function test_ManualExecutionNotYetEnabled_Revert() public { + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, BLOCK_TIME); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.ManualExecutionNotYetEnabled.selector, SOURCE_CHAIN_SELECTOR_1)); + + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), _getGasLimitsFromMessages(messages) + ); + } + + function test_NonExistingSourceChain_Revert() public { + uint64 newSourceChainSelector = SOURCE_CHAIN_SELECTOR_1 + 1; + bytes memory newOnRamp = abi.encode(ON_RAMP_ADDRESS, 1); + + Internal.Any2EVMRampMessage[] memory messages = _generateSingleBasicMessage(newSourceChainSelector, newOnRamp); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.SourceChainNotEnabled.selector, newSourceChainSelector)); + s_offRamp.executeSingleReport( + _generateReportFromMessages(newSourceChainSelector, messages), new OffRamp.GasLimitOverride[](0) + ); + } + + function test_DisabledSourceChain_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_2, ON_RAMP_ADDRESS_2); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.SourceChainNotEnabled.selector, SOURCE_CHAIN_SELECTOR_2)); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_2, messages), new OffRamp.GasLimitOverride[](0) + ); + } + + function test_TokenDataMismatch_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + report.offchainTokenData[0] = new bytes[](messages[0].tokenAmounts.length + 1); + + vm.expectRevert( + abi.encodeWithSelector( + OffRamp.TokenDataMismatch.selector, SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber + ) + ); + s_offRamp.executeSingleReport(report, new OffRamp.GasLimitOverride[](0)); + } + + function test_RouterYULCall_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + // gas limit too high, Router's external call should revert + messages[0].gasLimit = 1e36; + messages[0].receiver = address(new ConformingReceiver(address(s_destRouter), s_destFeeToken)); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + Internal.ExecutionReport memory executionReport = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + vm.recordLogs(); + s_offRamp.executeSingleReport(executionReport, new OffRamp.GasLimitOverride[](0)); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector(CallWithExactGas.NotEnoughGasForCall.selector) + ); + } + + function test_RetryFailedMessageWithoutManualExecution_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + bytes memory realError1 = new bytes(2); + realError1[0] = 0xbe; + realError1[1] = 0xef; + s_reverting_receiver.setErr(realError1); + + messages[0].receiver = address(s_reverting_receiver); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + assertExecutionStateChangedEventLogs( + messages[0].header.sourceChainSelector, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector( + OffRamp.ReceiverError.selector, + abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, realError1) + ) + ); + + // The second time should skip the msg + vm.expectEmit(); + emit OffRamp.AlreadyAttempted(SOURCE_CHAIN_SELECTOR_1, messages[0].header.sequenceNumber); + + s_offRamp.executeSingleReport( + _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[](0) + ); + } + + function _constructCommitReport( + bytes32 merkleRoot + ) internal view returns (OffRamp.CommitReport memory) { + Internal.MerkleRoot[] memory roots = new Internal.MerkleRoot[](1); + roots[0] = Internal.MerkleRoot({ + sourceChainSelector: SOURCE_CHAIN_SELECTOR_1, + onRampAddress: abi.encode(ON_RAMP_ADDRESS_1), + minSeqNr: 1, + maxSeqNr: 2, + merkleRoot: merkleRoot + }); + + return OffRamp.CommitReport({ + priceUpdates: _getSingleTokenPriceUpdateStruct(s_sourceFeeToken, 4e18), + merkleRoots: roots, + rmnSignatures: s_rmnSignatures + }); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.getExecutionState.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.getExecutionState.t.sol new file mode 100644 index 00000000000..9b8e719053b --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.getExecutionState.t.sol @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../../libraries/Internal.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +contract OffRamp_getExecutionState is OffRampSetup { + mapping(uint64 sourceChainSelector => mapping(uint64 seqNum => Internal.MessageExecutionState state)) internal + s_differentialExecutionState; + + /// forge-config: default.fuzz.runs = 32 + /// forge-config: ccip.fuzz.runs = 32 + function test_Fuzz_Differential_Success( + uint64 sourceChainSelector, + uint16[500] memory seqNums, + uint8[500] memory values + ) public { + for (uint256 i = 0; i < seqNums.length; ++i) { + // Only use the first three slots. This makes sure existing slots get overwritten + // as the tests uses 500 sequence numbers. + uint16 seqNum = seqNums[i] % 386; + Internal.MessageExecutionState state = Internal.MessageExecutionState(values[i] % 4); + s_differentialExecutionState[sourceChainSelector][seqNum] = state; + s_offRamp.setExecutionStateHelper(sourceChainSelector, seqNum, state); + assertEq(uint256(state), uint256(s_offRamp.getExecutionState(sourceChainSelector, seqNum))); + } + + for (uint256 i = 0; i < seqNums.length; ++i) { + uint16 seqNum = seqNums[i] % 386; + Internal.MessageExecutionState expectedState = s_differentialExecutionState[sourceChainSelector][seqNum]; + assertEq(uint256(expectedState), uint256(s_offRamp.getExecutionState(sourceChainSelector, seqNum))); + } + } + + function test_GetExecutionState_Success() public { + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 0, Internal.MessageExecutionState.FAILURE); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3); + + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 1, Internal.MessageExecutionState.FAILURE); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (3 << 2)); + + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 1, Internal.MessageExecutionState.IN_PROGRESS); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 2)); + + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 2, Internal.MessageExecutionState.FAILURE); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 2) + (3 << 4)); + + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 127, Internal.MessageExecutionState.IN_PROGRESS); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 2) + (3 << 4) + (1 << 254)); + + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 128, Internal.MessageExecutionState.SUCCESS); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 2) + (3 << 4) + (1 << 254)); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 1), 2); + + assertEq( + uint256(Internal.MessageExecutionState.FAILURE), uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 0)) + ); + assertEq( + uint256(Internal.MessageExecutionState.IN_PROGRESS), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 1)) + ); + assertEq( + uint256(Internal.MessageExecutionState.FAILURE), uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 2)) + ); + assertEq( + uint256(Internal.MessageExecutionState.IN_PROGRESS), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 127)) + ); + assertEq( + uint256(Internal.MessageExecutionState.SUCCESS), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 128)) + ); + } + + function test_GetDifferentChainExecutionState_Success() public { + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 0, Internal.MessageExecutionState.FAILURE); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 0), 0); + + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 127, Internal.MessageExecutionState.IN_PROGRESS); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 254)); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 0), 0); + + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, 128, Internal.MessageExecutionState.SUCCESS); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 254)); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 1), 2); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 0), 0); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 1), 0); + + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1 + 1, 127, Internal.MessageExecutionState.FAILURE); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 0), 3 + (1 << 254)); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, 1), 2); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 0), (3 << 254)); + assertEq(s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1 + 1, 1), 0); + + assertEq( + uint256(Internal.MessageExecutionState.FAILURE), uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 0)) + ); + assertEq( + uint256(Internal.MessageExecutionState.IN_PROGRESS), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 127)) + ); + assertEq( + uint256(Internal.MessageExecutionState.SUCCESS), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, 128)) + ); + + assertEq( + uint256(Internal.MessageExecutionState.UNTOUCHED), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1 + 1, 0)) + ); + assertEq( + uint256(Internal.MessageExecutionState.FAILURE), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1 + 1, 127)) + ); + assertEq( + uint256(Internal.MessageExecutionState.UNTOUCHED), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1 + 1, 128)) + ); + } + + function test_FillExecutionState_Success() public { + for (uint64 i = 0; i < 384; ++i) { + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, i, Internal.MessageExecutionState.FAILURE); + } + + for (uint64 i = 0; i < 384; ++i) { + assertEq( + uint256(Internal.MessageExecutionState.FAILURE), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, i)) + ); + } + + for (uint64 i = 0; i < 3; ++i) { + assertEq(type(uint256).max, s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, i)); + } + + for (uint64 i = 0; i < 384; ++i) { + s_offRamp.setExecutionStateHelper(SOURCE_CHAIN_SELECTOR_1, i, Internal.MessageExecutionState.IN_PROGRESS); + } + + for (uint64 i = 0; i < 384; ++i) { + assertEq( + uint256(Internal.MessageExecutionState.IN_PROGRESS), + uint256(s_offRamp.getExecutionState(SOURCE_CHAIN_SELECTOR_1, i)) + ); + } + + for (uint64 i = 0; i < 3; ++i) { + // 0x555... == 0b101010101010..... + assertEq( + 0x5555555555555555555555555555555555555555555555555555555555555555, + s_offRamp.getExecutionStateBitMap(SOURCE_CHAIN_SELECTOR_1, i) + ); + } + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol new file mode 100644 index 00000000000..91afbdfac8c --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.manuallyExecute.t.sol @@ -0,0 +1,584 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../../libraries/Internal.sol"; +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {ConformingReceiver} from "../../helpers/receivers/ConformingReceiver.sol"; +import {MaybeRevertMessageReceiver} from "../../helpers/receivers/MaybeRevertMessageReceiver.sol"; +import {ReentrancyAbuserMultiRamp} from "../../helpers/receivers/ReentrancyAbuserMultiRamp.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {Vm} from "forge-std/Vm.sol"; + +contract OffRamp_manuallyExecute is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_1, 1); + s_offRamp.setVerifyOverrideResult(SOURCE_CHAIN_SELECTOR_3, 1); + } + + function test_manuallyExecute_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + messages[0].receiver = address(s_reverting_receiver); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + s_offRamp.batchExecute( + _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) + ); + + s_reverting_receiver.setRevert(false); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](messages.length); + + vm.recordLogs(); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_manuallyExecute_WithGasOverride_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + messages[0].receiver = address(s_reverting_receiver); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + s_offRamp.batchExecute( + _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) + ); + + s_reverting_receiver.setRevert(false); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); + gasLimitOverrides[0][0].receiverExecutionGasLimit += 1; + vm.recordLogs(); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_manuallyExecute_DoesNotRevertIfUntouched_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + messages[0].receiver = address(s_reverting_receiver); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + assertEq( + messages[0].header.nonce - 1, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender) + ); + + s_reverting_receiver.setRevert(true); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); + + vm.recordLogs(); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector( + OffRamp.ReceiverError.selector, abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, "") + ) + ); + + assertEq( + messages[0].header.nonce, s_inboundNonceManager.getInboundNonce(SOURCE_CHAIN_SELECTOR_1, messages[0].sender) + ); + } + + function test_manuallyExecute_WithMultiReportGasOverride_Success() public { + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](3); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](2); + + for (uint64 i = 0; i < 3; ++i) { + messages1[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, i + 1); + messages1[i].receiver = address(s_reverting_receiver); + messages1[i].header.messageId = _hashMessage(messages1[i], ON_RAMP_ADDRESS_1); + } + + for (uint64 i = 0; i < 2; ++i) { + messages2[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, i + 1); + messages2[i].receiver = address(s_reverting_receiver); + messages2[i].header.messageId = _hashMessage(messages2[i], ON_RAMP_ADDRESS_3); + } + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); + + s_offRamp.batchExecute(reports, new OffRamp.GasLimitOverride[][](2)); + + s_reverting_receiver.setRevert(false); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](2); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages1); + gasLimitOverrides[1] = _getGasLimitsFromMessages(messages2); + + for (uint256 i = 0; i < 3; ++i) { + gasLimitOverrides[0][i].receiverExecutionGasLimit += 1; + } + + for (uint256 i = 0; i < 2; ++i) { + gasLimitOverrides[1][i].receiverExecutionGasLimit += 1; + } + + vm.recordLogs(); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + for (uint256 j = 0; j < 3; ++j) { + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages1[j].header.sequenceNumber, + messages1[j].header.messageId, + _hashMessage(messages1[j], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + for (uint256 k = 0; k < 2; ++k) { + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_3, + messages2[k].header.sequenceNumber, + messages2[k].header.messageId, + _hashMessage(messages2[k], ON_RAMP_ADDRESS_3), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + } + + function test_manuallyExecute_WithPartialMessages_Success() public { + Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](3); + + for (uint64 i = 0; i < 3; ++i) { + messages[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, i + 1); + } + + messages[1].receiver = address(s_reverting_receiver); + messages[1].header.messageId = _hashMessage(messages[1], ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.batchExecute( + _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) + ); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages[1].header.sequenceNumber, + messages[1].header.messageId, + _hashMessage(messages[1], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector( + OffRamp.ReceiverError.selector, + abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, bytes("")) + ) + ); + + assertExecutionStateChangedEventLogs( + logs, + SOURCE_CHAIN_SELECTOR_1, + messages[2].header.sequenceNumber, + messages[2].header.messageId, + _hashMessage(messages[2], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + s_reverting_receiver.setRevert(false); + + // Only the 2nd message reverted + Internal.Any2EVMRampMessage[] memory newMessages = new Internal.Any2EVMRampMessage[](1); + newMessages[0] = messages[1]; + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(newMessages); + gasLimitOverrides[0][0].receiverExecutionGasLimit += 1; + + vm.recordLogs(); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, newMessages), gasLimitOverrides); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + function test_manuallyExecute_LowGasLimit_Success() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + messages[0].gasLimit = 1; + messages[0].receiver = address(new ConformingReceiver(address(s_destRouter), s_destFeeToken)); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + vm.recordLogs(); + s_offRamp.batchExecute( + _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) + ); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.FAILURE, + abi.encodeWithSelector(OffRamp.ReceiverError.selector, "") + ); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](1); + gasLimitOverrides[0][0].receiverExecutionGasLimit = 100_000; + + vm.expectEmit(); + emit ConformingReceiver.MessageReceived(); + + vm.recordLogs(); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + } + + // Reverts + + function test_manuallyExecute_ForkedChain_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + uint256 chain1 = block.chainid; + uint256 chain2 = chain1 + 1; + vm.chainId(chain2); + vm.expectRevert(abi.encodeWithSelector(MultiOCR3Base.ForkedChain.selector, chain1, chain2)); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); + + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + } + + function test_ManualExecGasLimitMismatchSingleReport_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](2); + messages[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); + + Internal.ExecutionReport[] memory reports = _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + // No overrides for report + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, new OffRamp.GasLimitOverride[][](0)); + + // No messages + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + + // 1 message missing + gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](1); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + + // 1 message in excess + gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](3); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + } + + function test_manuallyExecute_GasLimitMismatchMultipleReports_Revert() public { + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](2); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); + + messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages1[1] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 2); + messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, 1); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, new OffRamp.GasLimitOverride[][](0)); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, new OffRamp.GasLimitOverride[][](1)); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](2); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + + // 2nd report empty + gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](2); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + + // 1st report empty + gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](0); + gasLimitOverrides[1] = new OffRamp.GasLimitOverride[](1); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + + // 1st report oversized + gasLimitOverrides[0] = new OffRamp.GasLimitOverride[](3); + + vm.expectRevert(OffRamp.ManualExecutionGasLimitMismatch.selector); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + } + + function test_manuallyExecute_InvalidReceiverExecutionGasLimit_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); + gasLimitOverrides[0][0].receiverExecutionGasLimit--; + + vm.expectRevert( + abi.encodeWithSelector( + OffRamp.InvalidManualExecutionGasLimit.selector, + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.messageId, + gasLimitOverrides[0][0].receiverExecutionGasLimit + ) + ); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + } + + function test_manuallyExecute_DestinationGasAmountCountMismatch_Revert() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 1000; + Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](1); + messages[0] = _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); + // empty tokenGasOverride array provided + vm.expectRevert( + abi.encodeWithSelector(OffRamp.ManualExecutionGasAmountCountMismatch.selector, messages[0].header.messageId, 1) + ); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + + //trying with excesss elements tokenGasOverride array provided + gasLimitOverrides[0][0].tokenGasOverrides = new uint32[](3); + vm.expectRevert( + abi.encodeWithSelector(OffRamp.ManualExecutionGasAmountCountMismatch.selector, messages[0].header.messageId, 1) + ); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + } + + function test_manuallyExecute_InvalidTokenGasOverride_Revert() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 1000; + Internal.Any2EVMRampMessage[] memory messages = new Internal.Any2EVMRampMessage[](1); + messages[0] = _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); + uint32[] memory tokenGasOverrides = new uint32[](2); + tokenGasOverrides[0] = DEFAULT_TOKEN_DEST_GAS_OVERHEAD; + tokenGasOverrides[1] = DEFAULT_TOKEN_DEST_GAS_OVERHEAD - 1; //invalid token gas override value + gasLimitOverrides[0][0].tokenGasOverrides = tokenGasOverrides; + + vm.expectRevert( + abi.encodeWithSelector( + OffRamp.InvalidManualExecutionTokenGasOverride.selector, + messages[0].header.messageId, + 1, + DEFAULT_TOKEN_DEST_GAS_OVERHEAD, + tokenGasOverrides[1] + ) + ); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + } + + function test_manuallyExecute_FailedTx_Revert() public { + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + + messages[0].receiver = address(s_reverting_receiver); + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + s_offRamp.batchExecute( + _generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), new OffRamp.GasLimitOverride[][](1) + ); + + s_reverting_receiver.setRevert(true); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); + + vm.expectRevert( + abi.encodeWithSelector( + OffRamp.ExecutionError.selector, + messages[0].header.messageId, + abi.encodeWithSelector( + OffRamp.ReceiverError.selector, + abi.encodeWithSelector(MaybeRevertMessageReceiver.CustomError.selector, bytes("")) + ) + ) + ); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + } + + function test_manuallyExecute_ReentrancyFails_Success() public { + uint256 tokenAmount = 1e9; + IERC20 tokenToAbuse = IERC20(s_destFeeToken); + + // This needs to be deployed before the source chain message is sent + // because we need the address for the receiver. + ReentrancyAbuserMultiRamp receiver = new ReentrancyAbuserMultiRamp(address(s_destRouter), s_offRamp); + uint256 balancePre = tokenToAbuse.balanceOf(address(receiver)); + + // For this test any message will be flagged as correct by the + // commitStore. In a real scenario the abuser would have to actually + // send the message that they want to replay. + Internal.Any2EVMRampMessage[] memory messages = + _generateSingleBasicMessage(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1); + messages[0].tokenAmounts = new Internal.Any2EVMTokenTransfer[](1); + messages[0].tokenAmounts[0] = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[s_sourceFeeToken]), + destTokenAddress: s_destTokenBySourceToken[s_sourceFeeToken], + extraData: "", + amount: tokenAmount, + destGasAmount: MAX_TOKEN_POOL_RELEASE_OR_MINT_GAS + }); + + messages[0].receiver = address(receiver); + + messages[0].header.messageId = _hashMessage(messages[0], ON_RAMP_ADDRESS_1); + + Internal.ExecutionReport memory report = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages); + + // sets the report to be repeated on the ReentrancyAbuser to be able to replay + receiver.setPayload(report); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](1); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages); + gasLimitOverrides[0][0].tokenGasOverrides = new uint32[](messages[0].tokenAmounts.length); + + // The first entry should be fine and triggers the second entry which is skipped. Due to the reentrancy + // the second completes first, so we expect the skip event before the success event. + vm.expectEmit(); + emit OffRamp.SkippedAlreadyExecutedMessage( + messages[0].header.sourceChainSelector, messages[0].header.sequenceNumber + ); + + vm.recordLogs(); + s_offRamp.manuallyExecute(_generateBatchReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages), gasLimitOverrides); + assertExecutionStateChangedEventLogs( + SOURCE_CHAIN_SELECTOR_1, + messages[0].header.sequenceNumber, + messages[0].header.messageId, + _hashMessage(messages[0], ON_RAMP_ADDRESS_1), + Internal.MessageExecutionState.SUCCESS, + "" + ); + + // Since the tx failed we don't release the tokens + assertEq(tokenToAbuse.balanceOf(address(receiver)), balancePre + tokenAmount); + } + + function test_manuallyExecute_MultipleReportsWithSingleCursedLane_Revert() public { + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](3); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](2); + + for (uint64 i = 0; i < 3; ++i) { + messages1[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, i + 1); + messages1[i].receiver = address(s_reverting_receiver); + messages1[i].header.messageId = _hashMessage(messages1[i], ON_RAMP_ADDRESS_1); + } + + for (uint64 i = 0; i < 2; ++i) { + messages2[i] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_3, ON_RAMP_ADDRESS_3, i + 1); + messages2[i].receiver = address(s_reverting_receiver); + messages2[i].header.messageId = _hashMessage(messages2[i], ON_RAMP_ADDRESS_3); + } + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](2); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages1); + gasLimitOverrides[1] = _getGasLimitsFromMessages(messages2); + + _setMockRMNChainCurse(SOURCE_CHAIN_SELECTOR_3, true); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.CursedByRMN.selector, SOURCE_CHAIN_SELECTOR_3)); + + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + } + + function test_manuallyExecute_SourceChainSelectorMismatch_Revert() public { + Internal.Any2EVMRampMessage[] memory messages1 = new Internal.Any2EVMRampMessage[](1); + Internal.Any2EVMRampMessage[] memory messages2 = new Internal.Any2EVMRampMessage[](1); + messages1[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + messages2[0] = _generateAny2EVMMessageNoTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1); + + Internal.ExecutionReport[] memory reports = new Internal.ExecutionReport[](2); + reports[0] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_1, messages1); + reports[1] = _generateReportFromMessages(SOURCE_CHAIN_SELECTOR_3, messages2); + + OffRamp.GasLimitOverride[][] memory gasLimitOverrides = new OffRamp.GasLimitOverride[][](2); + gasLimitOverrides[0] = _getGasLimitsFromMessages(messages1); + gasLimitOverrides[1] = _getGasLimitsFromMessages(messages2); + + vm.expectRevert( + abi.encodeWithSelector( + OffRamp.SourceChainSelectorMismatch.selector, SOURCE_CHAIN_SELECTOR_3, SOURCE_CHAIN_SELECTOR_1 + ) + ); + s_offRamp.manuallyExecute(reports, gasLimitOverrides); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintSingleToken.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintSingleToken.t.sol new file mode 100644 index 00000000000..72999fad42f --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintSingleToken.t.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {ITokenAdminRegistry} from "../../../interfaces/ITokenAdminRegistry.sol"; + +import {Internal} from "../../../libraries/Internal.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OffRamp_releaseOrMintSingleToken is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + } + + function test__releaseOrMintSingleToken_Success() public { + uint256 amount = 123123; + address token = s_sourceTokens[0]; + bytes memory originalSender = abi.encode(OWNER); + bytes memory offchainTokenData = abi.encode(keccak256("offchainTokenData")); + + IERC20 dstToken1 = IERC20(s_destTokenBySourceToken[token]); + uint256 startingBalance = dstToken1.balanceOf(OWNER); + + Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), + destTokenAddress: s_destTokenBySourceToken[token], + extraData: "", + amount: amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + vm.expectCall( + s_destPoolBySourceToken[token], + abi.encodeWithSelector( + LockReleaseTokenPool.releaseOrMint.selector, + Pool.ReleaseOrMintInV1({ + originalSender: originalSender, + receiver: OWNER, + amount: amount, + localToken: s_destTokenBySourceToken[token], + remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, + sourcePoolAddress: tokenAmount.sourcePoolAddress, + sourcePoolData: tokenAmount.extraData, + offchainTokenData: offchainTokenData + }) + ) + ); + + s_offRamp.releaseOrMintSingleToken(tokenAmount, originalSender, OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData); + + assertEq(startingBalance + amount, dstToken1.balanceOf(OWNER)); + } + + function test_releaseOrMintToken_InvalidDataLength_Revert() public { + uint256 amount = 123123; + address token = s_sourceTokens[0]; + + Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), + destTokenAddress: s_destTokenBySourceToken[token], + extraData: "", + amount: amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + // Mock the call so returns 2 slots of data + vm.mockCall( + s_destTokenBySourceToken[token], abi.encodeWithSelector(IERC20.balanceOf.selector, OWNER), abi.encode(0, 0) + ); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.InvalidDataLength.selector, Internal.MAX_BALANCE_OF_RET_BYTES, 64)); + + s_offRamp.releaseOrMintSingleToken(tokenAmount, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR, ""); + } + + function test_releaseOrMintToken_TokenHandlingError_BalanceOf_Revert() public { + uint256 amount = 123123; + address token = s_sourceTokens[0]; + + Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), + destTokenAddress: s_destTokenBySourceToken[token], + extraData: "", + amount: amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + bytes memory revertData = "failed to balanceOf"; + + // Mock the call so returns 2 slots of data + vm.mockCallRevert( + s_destTokenBySourceToken[token], abi.encodeWithSelector(IERC20.balanceOf.selector, OWNER), revertData + ); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, revertData)); + + s_offRamp.releaseOrMintSingleToken(tokenAmount, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR, ""); + } + + function test_releaseOrMintToken_ReleaseOrMintBalanceMismatch_Revert() public { + uint256 amount = 123123; + address token = s_sourceTokens[0]; + uint256 mockedStaticBalance = 50000; + + Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), + destTokenAddress: s_destTokenBySourceToken[token], + extraData: "", + amount: amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + vm.mockCall( + s_destTokenBySourceToken[token], + abi.encodeWithSelector(IERC20.balanceOf.selector, OWNER), + abi.encode(mockedStaticBalance) + ); + + vm.expectRevert( + abi.encodeWithSelector( + OffRamp.ReleaseOrMintBalanceMismatch.selector, amount, mockedStaticBalance, mockedStaticBalance + ) + ); + + s_offRamp.releaseOrMintSingleToken(tokenAmount, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR, ""); + } + + function test_releaseOrMintToken_skip_ReleaseOrMintBalanceMismatch_if_pool_Revert() public { + uint256 amount = 123123; + address token = s_sourceTokens[0]; + uint256 mockedStaticBalance = 50000; + + Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), + destTokenAddress: s_destTokenBySourceToken[token], + extraData: "", + amount: amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + // This should make the call fail if it does not skip the check + vm.mockCall( + s_destTokenBySourceToken[token], + abi.encodeWithSelector(IERC20.balanceOf.selector, OWNER), + abi.encode(mockedStaticBalance) + ); + + s_offRamp.releaseOrMintSingleToken( + tokenAmount, abi.encode(OWNER), s_destPoolBySourceToken[token], SOURCE_CHAIN_SELECTOR, "" + ); + } + + function test__releaseOrMintSingleToken_NotACompatiblePool_Revert() public { + uint256 amount = 123123; + address token = s_sourceTokens[0]; + address destToken = s_destTokenBySourceToken[token]; + vm.label(destToken, "destToken"); + bytes memory originalSender = abi.encode(OWNER); + bytes memory offchainTokenData = abi.encode(keccak256("offchainTokenData")); + + Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), + destTokenAddress: destToken, + extraData: "", + amount: amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + // Address(0) should always revert + address returnedPool = address(0); + + vm.mockCall( + address(s_tokenAdminRegistry), + abi.encodeWithSelector(ITokenAdminRegistry.getPool.selector, destToken), + abi.encode(returnedPool) + ); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, returnedPool)); + + s_offRamp.releaseOrMintSingleToken(tokenAmount, originalSender, OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData); + + // A contract that doesn't support the interface should also revert + returnedPool = address(s_offRamp); + + vm.mockCall( + address(s_tokenAdminRegistry), + abi.encodeWithSelector(ITokenAdminRegistry.getPool.selector, destToken), + abi.encode(returnedPool) + ); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, returnedPool)); + + s_offRamp.releaseOrMintSingleToken(tokenAmount, originalSender, OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData); + } + + function test__releaseOrMintSingleToken_TokenHandlingError_transfer_Revert() public { + address receiver = makeAddr("receiver"); + uint256 amount = 123123; + address token = s_sourceTokens[0]; + address destToken = s_destTokenBySourceToken[token]; + bytes memory originalSender = abi.encode(OWNER); + bytes memory offchainTokenData = abi.encode(keccak256("offchainTokenData")); + + Internal.Any2EVMTokenTransfer memory tokenAmount = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(s_sourcePoolByToken[token]), + destTokenAddress: destToken, + extraData: "", + amount: amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + bytes memory revertData = "call reverted :o"; + + vm.mockCallRevert(destToken, abi.encodeWithSelector(IERC20.transfer.selector, receiver, amount), revertData); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, revertData)); + s_offRamp.releaseOrMintSingleToken( + tokenAmount, originalSender, receiver, SOURCE_CHAIN_SELECTOR_1, offchainTokenData + ); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol new file mode 100644 index 00000000000..40a4514eb70 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.releaseOrMintTokens.t.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {CallWithExactGas} from "../../../../shared/call/CallWithExactGas.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {Pool} from "../../../libraries/Pool.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {LockReleaseTokenPool} from "../../../pools/LockReleaseTokenPool.sol"; +import {MaybeRevertingBurnMintTokenPool} from "../../helpers/MaybeRevertingBurnMintTokenPool.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OffRamp_releaseOrMintTokens is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + } + + function test_releaseOrMintTokens_Success() public { + Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); + IERC20 dstToken1 = IERC20(s_destFeeToken); + uint256 startingBalance = dstToken1.balanceOf(OWNER); + uint256 amount1 = 100; + srcTokenAmounts[0].amount = amount1; + + bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); + offchainTokenData[0] = abi.encode(0x12345678); + + Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); + + vm.expectCall( + s_destPoolBySourceToken[srcTokenAmounts[0].token], + abi.encodeWithSelector( + LockReleaseTokenPool.releaseOrMint.selector, + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: OWNER, + amount: srcTokenAmounts[0].amount, + localToken: s_destTokenBySourceToken[srcTokenAmounts[0].token], + remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, + sourcePoolAddress: sourceTokenAmounts[0].sourcePoolAddress, + sourcePoolData: sourceTokenAmounts[0].extraData, + offchainTokenData: offchainTokenData[0] + }) + ) + ); + + s_offRamp.releaseOrMintTokens( + sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData, new uint32[](0) + ); + + assertEq(startingBalance + amount1, dstToken1.balanceOf(OWNER)); + } + + function test_releaseOrMintTokens_WithGasOverride_Success() public { + Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); + IERC20 dstToken1 = IERC20(s_destFeeToken); + uint256 startingBalance = dstToken1.balanceOf(OWNER); + uint256 amount1 = 100; + srcTokenAmounts[0].amount = amount1; + + bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); + offchainTokenData[0] = abi.encode(0x12345678); + + Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); + + vm.expectCall( + s_destPoolBySourceToken[srcTokenAmounts[0].token], + abi.encodeWithSelector( + LockReleaseTokenPool.releaseOrMint.selector, + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: OWNER, + amount: srcTokenAmounts[0].amount, + localToken: s_destTokenBySourceToken[srcTokenAmounts[0].token], + remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, + sourcePoolAddress: sourceTokenAmounts[0].sourcePoolAddress, + sourcePoolData: sourceTokenAmounts[0].extraData, + offchainTokenData: offchainTokenData[0] + }) + ) + ); + + uint32[] memory gasOverrides = new uint32[](sourceTokenAmounts.length); + for (uint256 i = 0; i < gasOverrides.length; i++) { + gasOverrides[i] = DEFAULT_TOKEN_DEST_GAS_OVERHEAD + 1; + } + s_offRamp.releaseOrMintTokens( + sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData, gasOverrides + ); + + assertEq(startingBalance + amount1, dstToken1.balanceOf(OWNER)); + } + + function test_releaseOrMintTokens_destDenominatedDecimals_Success() public { + Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); + uint256 amount = 100; + uint256 destinationDenominationMultiplier = 1000; + srcTokenAmounts[1].amount = amount; + + bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); + + Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); + + address pool = s_destPoolBySourceToken[srcTokenAmounts[1].token]; + address destToken = s_destTokenBySourceToken[srcTokenAmounts[1].token]; + + MaybeRevertingBurnMintTokenPool(pool).setReleaseOrMintMultiplier(destinationDenominationMultiplier); + + Client.EVMTokenAmount[] memory destTokenAmounts = s_offRamp.releaseOrMintTokens( + sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData, new uint32[](0) + ); + assertEq(destTokenAmounts[1].amount, amount * destinationDenominationMultiplier); + assertEq(destTokenAmounts[1].token, destToken); + } + + // Revert + + function test_TokenHandlingError_Reverts() public { + Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); + + bytes memory unknownError = bytes("unknown error"); + s_maybeRevertingPool.setShouldRevert(unknownError); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, unknownError)); + + s_offRamp.releaseOrMintTokens( + _getDefaultSourceTokenData(srcTokenAmounts), + abi.encode(OWNER), + OWNER, + SOURCE_CHAIN_SELECTOR_1, + new bytes[](srcTokenAmounts.length), + new uint32[](0) + ); + } + + function test_releaseOrMintTokens_InvalidDataLengthReturnData_Revert() public { + uint256 amount = 100; + Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); + srcTokenAmounts[0].amount = amount; + + bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); + Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); + + vm.mockCall( + s_destPoolBySourceToken[srcTokenAmounts[0].token], + abi.encodeWithSelector( + LockReleaseTokenPool.releaseOrMint.selector, + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: OWNER, + amount: amount, + localToken: s_destTokenBySourceToken[srcTokenAmounts[0].token], + remoteChainSelector: SOURCE_CHAIN_SELECTOR_1, + sourcePoolAddress: sourceTokenAmounts[0].sourcePoolAddress, + sourcePoolData: sourceTokenAmounts[0].extraData, + offchainTokenData: offchainTokenData[0] + }) + ), + // Includes the amount twice, this will revert due to the return data being to long + abi.encode(amount, amount) + ); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.InvalidDataLength.selector, Pool.CCIP_LOCK_OR_BURN_V1_RET_BYTES, 64)); + + s_offRamp.releaseOrMintTokens( + sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, offchainTokenData, new uint32[](0) + ); + } + + function test__releaseOrMintTokens_PoolIsNotAPool_Reverts() public { + // The offRamp is a contract, but not a pool + address fakePoolAddress = address(s_offRamp); + + Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = new Internal.Any2EVMTokenTransfer[](1); + sourceTokenAmounts[0] = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(fakePoolAddress), + destTokenAddress: address(s_offRamp), + extraData: "", + amount: 1, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + vm.expectRevert(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, address(0))); + s_offRamp.releaseOrMintTokens( + sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, new bytes[](1), new uint32[](0) + ); + } + + function test_releaseOrMintTokens_PoolDoesNotSupportDest_Reverts() public { + Client.EVMTokenAmount[] memory srcTokenAmounts = _getCastedSourceEVMTokenAmountsWithZeroAmounts(); + uint256 amount1 = 100; + srcTokenAmounts[0].amount = amount1; + + bytes[] memory offchainTokenData = new bytes[](srcTokenAmounts.length); + offchainTokenData[0] = abi.encode(0x12345678); + + Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = _getDefaultSourceTokenData(srcTokenAmounts); + + vm.expectCall( + s_destPoolBySourceToken[srcTokenAmounts[0].token], + abi.encodeWithSelector( + LockReleaseTokenPool.releaseOrMint.selector, + Pool.ReleaseOrMintInV1({ + originalSender: abi.encode(OWNER), + receiver: OWNER, + amount: srcTokenAmounts[0].amount, + localToken: s_destTokenBySourceToken[srcTokenAmounts[0].token], + remoteChainSelector: SOURCE_CHAIN_SELECTOR_3, + sourcePoolAddress: sourceTokenAmounts[0].sourcePoolAddress, + sourcePoolData: sourceTokenAmounts[0].extraData, + offchainTokenData: offchainTokenData[0] + }) + ) + ); + vm.expectRevert(); + s_offRamp.releaseOrMintTokens( + sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_3, offchainTokenData, new uint32[](0) + ); + } + + /// forge-config: default.fuzz.runs = 32 + /// forge-config: ccip.fuzz.runs = 1024 + // Uint256 gives a good range of values to test, both inside and outside of the eth address space. + function test_Fuzz__releaseOrMintTokens_AnyRevertIsCaught_Success( + address destPool + ) public { + // Input 447301751254033913445893214690834296930546521452, which is 0x4E59B44847B379578588920CA78FBF26C0B4956C + // triggers some Create2Deployer and causes it to fail + vm.assume(destPool != 0x4e59b44847b379578588920cA78FbF26c0B4956C); + bytes memory unusedVar = abi.encode(makeAddr("unused")); + Internal.Any2EVMTokenTransfer[] memory sourceTokenAmounts = new Internal.Any2EVMTokenTransfer[](1); + sourceTokenAmounts[0] = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: unusedVar, + destTokenAddress: destPool, + extraData: unusedVar, + amount: 1, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + try s_offRamp.releaseOrMintTokens( + sourceTokenAmounts, abi.encode(OWNER), OWNER, SOURCE_CHAIN_SELECTOR_1, new bytes[](1), new uint32[](0) + ) {} catch (bytes memory reason) { + // Any revert should be a TokenHandlingError, InvalidEVMAddress, InvalidDataLength or NoContract as those are caught by the offramp + assertTrue( + bytes4(reason) == OffRamp.TokenHandlingError.selector || bytes4(reason) == Internal.InvalidEVMAddress.selector + || bytes4(reason) == OffRamp.InvalidDataLength.selector + || bytes4(reason) == CallWithExactGas.NoContract.selector + || bytes4(reason) == OffRamp.NotACompatiblePool.selector, + "Expected TokenHandlingError or InvalidEVMAddress" + ); + + if (uint160(destPool) > type(uint160).max) { + assertEq(reason, abi.encodeWithSelector(Internal.InvalidEVMAddress.selector, abi.encode(destPool))); + } + } + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol new file mode 100644 index 00000000000..2b67f09c0e1 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.setDynamicConfig.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Ownable2Step} from "../../../../shared/access/Ownable2Step.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +contract OffRamp_setDynamicConfig is OffRampSetup { + function test_SetDynamicConfig_Success() public { + OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); + + vm.expectEmit(); + emit OffRamp.DynamicConfigSet(dynamicConfig); + + s_offRamp.setDynamicConfig(dynamicConfig); + + OffRamp.DynamicConfig memory newConfig = s_offRamp.getDynamicConfig(); + _assertSameConfig(dynamicConfig, newConfig); + } + + function test_SetDynamicConfigWithInterceptor_Success() public { + OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); + dynamicConfig.messageInterceptor = address(s_inboundMessageInterceptor); + + vm.expectEmit(); + emit OffRamp.DynamicConfigSet(dynamicConfig); + + s_offRamp.setDynamicConfig(dynamicConfig); + + OffRamp.DynamicConfig memory newConfig = s_offRamp.getDynamicConfig(); + _assertSameConfig(dynamicConfig, newConfig); + } + + // Reverts + + function test_NonOwner_Revert() public { + vm.startPrank(STRANGER); + OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(address(s_feeQuoter)); + + vm.expectRevert(Ownable2Step.OnlyCallableByOwner.selector); + + s_offRamp.setDynamicConfig(dynamicConfig); + } + + function test_FeeQuoterZeroAddress_Revert() public { + OffRamp.DynamicConfig memory dynamicConfig = _generateDynamicOffRampConfig(ZERO_ADDRESS); + + vm.expectRevert(OffRamp.ZeroAddressNotAllowed.selector); + + s_offRamp.setDynamicConfig(dynamicConfig); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.trialExecute.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.trialExecute.t.sol new file mode 100644 index 00000000000..8e944b91ab3 --- /dev/null +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRamp.trialExecute.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.24; + +import {Internal} from "../../../libraries/Internal.sol"; +import {RateLimiter} from "../../../libraries/RateLimiter.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {OffRampSetup} from "./OffRampSetup.t.sol"; + +import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; + +contract OffRamp_trialExecute is OffRampSetup { + function setUp() public virtual override { + super.setUp(); + _setupMultipleOffRamps(); + } + + function test_trialExecute_Success() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 50; + + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); + IERC20 dstToken0 = IERC20(s_destTokens[0]); + uint256 startingBalance = dstToken0.balanceOf(message.receiver); + + (Internal.MessageExecutionState newState, bytes memory err) = + s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + assertEq(uint256(Internal.MessageExecutionState.SUCCESS), uint256(newState)); + assertEq("", err); + + // Check that the tokens were transferred + assertEq(startingBalance + amounts[0], dstToken0.balanceOf(message.receiver)); + } + + function test_TokenHandlingErrorIsCaught_Success() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 50; + + IERC20 dstToken0 = IERC20(s_destTokens[0]); + uint256 startingBalance = dstToken0.balanceOf(OWNER); + + bytes memory errorMessage = "Random token pool issue"; + + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); + s_maybeRevertingPool.setShouldRevert(errorMessage); + + (Internal.MessageExecutionState newState, bytes memory err) = + s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + assertEq(uint256(Internal.MessageExecutionState.FAILURE), uint256(newState)); + assertEq(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, errorMessage), err); + + // Expect the balance to remain the same + assertEq(startingBalance, dstToken0.balanceOf(OWNER)); + } + + function test_RateLimitError_Success() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 50; + + bytes memory errorMessage = abi.encodeWithSelector(RateLimiter.BucketOverfilled.selector); + + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); + s_maybeRevertingPool.setShouldRevert(errorMessage); + + (Internal.MessageExecutionState newState, bytes memory err) = + s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + assertEq(uint256(Internal.MessageExecutionState.FAILURE), uint256(newState)); + assertEq(abi.encodeWithSelector(OffRamp.TokenHandlingError.selector, errorMessage), err); + } + + // TODO test actual pool exists but isn't compatible instead of just no pool + function test_TokenPoolIsNotAContract_Success() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 10000; + Internal.Any2EVMRampMessage memory message = + _generateAny2EVMMessageWithTokens(SOURCE_CHAIN_SELECTOR_1, ON_RAMP_ADDRESS_1, 1, amounts); + + // Happy path, pool is correct + (Internal.MessageExecutionState newState, bytes memory err) = + s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + + assertEq(uint256(Internal.MessageExecutionState.SUCCESS), uint256(newState)); + assertEq("", err); + + // address 0 has no contract + assertEq(address(0).code.length, 0); + + message.tokenAmounts[0] = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(address(0)), + destTokenAddress: address(0), + extraData: "", + amount: message.tokenAmounts[0].amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + message.header.messageId = _hashMessage(message, ON_RAMP_ADDRESS_1); + + // Unhappy path, no revert but marked as failed. + (newState, err) = s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + + assertEq(uint256(Internal.MessageExecutionState.FAILURE), uint256(newState)); + assertEq(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, address(0)), err); + + address notAContract = makeAddr("not_a_contract"); + + message.tokenAmounts[0] = Internal.Any2EVMTokenTransfer({ + sourcePoolAddress: abi.encode(address(0)), + destTokenAddress: notAContract, + extraData: "", + amount: message.tokenAmounts[0].amount, + destGasAmount: DEFAULT_TOKEN_DEST_GAS_OVERHEAD + }); + + message.header.messageId = _hashMessage(message, ON_RAMP_ADDRESS_1); + + (newState, err) = s_offRamp.trialExecute(message, new bytes[](message.tokenAmounts.length), new uint32[](0)); + + assertEq(uint256(Internal.MessageExecutionState.FAILURE), uint256(newState)); + assertEq(abi.encodeWithSelector(OffRamp.NotACompatiblePool.selector, address(0)), err); + } +} diff --git a/contracts/src/v0.8/ccip/test/offRamp/OffRampSetup.t.sol b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol similarity index 92% rename from contracts/src/v0.8/ccip/test/offRamp/OffRampSetup.t.sol rename to contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol index 3b43e1ad0be..7b9d422df15 100644 --- a/contracts/src/v0.8/ccip/test/offRamp/OffRampSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/offRamp/offRamp/OffRampSetup.t.sol @@ -1,23 +1,23 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.24; -import {IAny2EVMMessageReceiver} from "../../interfaces/IAny2EVMMessageReceiver.sol"; -import {IRMNRemote} from "../../interfaces/IRMNRemote.sol"; - -import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol"; -import {NonceManager} from "../../NonceManager.sol"; -import {Router} from "../../Router.sol"; -import {Client} from "../../libraries/Client.sol"; -import {Internal} from "../../libraries/Internal.sol"; -import {MultiOCR3Base} from "../../ocr/MultiOCR3Base.sol"; -import {OffRamp} from "../../offRamp/OffRamp.sol"; -import {TokenPool} from "../../pools/TokenPool.sol"; -import {FeeQuoterSetup} from "../feeQuoter/FeeQuoterSetup.t.sol"; -import {MaybeRevertingBurnMintTokenPool} from "../helpers/MaybeRevertingBurnMintTokenPool.sol"; -import {MessageInterceptorHelper} from "../helpers/MessageInterceptorHelper.sol"; -import {OffRampHelper} from "../helpers/OffRampHelper.sol"; -import {MaybeRevertMessageReceiver} from "../helpers/receivers/MaybeRevertMessageReceiver.sol"; -import {MultiOCR3BaseSetup} from "../ocr/MultiOCR3BaseSetup.t.sol"; +import {IAny2EVMMessageReceiver} from "../../../interfaces/IAny2EVMMessageReceiver.sol"; +import {IRMNRemote} from "../../../interfaces/IRMNRemote.sol"; + +import {AuthorizedCallers} from "../../../../shared/access/AuthorizedCallers.sol"; +import {NonceManager} from "../../../NonceManager.sol"; +import {Router} from "../../../Router.sol"; +import {Client} from "../../../libraries/Client.sol"; +import {Internal} from "../../../libraries/Internal.sol"; +import {MultiOCR3Base} from "../../../ocr/MultiOCR3Base.sol"; +import {OffRamp} from "../../../offRamp/OffRamp.sol"; +import {TokenPool} from "../../../pools/TokenPool.sol"; +import {FeeQuoterSetup} from "../../feeQuoter/FeeQuoterSetup.t.sol"; +import {MaybeRevertingBurnMintTokenPool} from "../../helpers/MaybeRevertingBurnMintTokenPool.sol"; +import {MessageInterceptorHelper} from "../../helpers/MessageInterceptorHelper.sol"; +import {OffRampHelper} from "../../helpers/OffRampHelper.sol"; +import {MaybeRevertMessageReceiver} from "../../helpers/receivers/MaybeRevertMessageReceiver.sol"; +import {MultiOCR3BaseSetup} from "../../ocr/MultiOCR3BaseSetup.t.sol"; import {Vm} from "forge-std/Test.sol"; contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { @@ -44,8 +44,8 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { bytes32 internal s_configDigestExec; bytes32 internal s_configDigestCommit; - uint64 internal constant s_offchainConfigVersion = 3; - uint8 internal constant s_F = 1; + uint64 internal constant OFFCHAIN_CONFIG_VERSION = 3; + uint8 internal constant F = 1; uint64 internal s_latestSequenceNumber; @@ -80,14 +80,14 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { sourceChainConfigs ); - s_configDigestExec = _getBasicConfigDigest(s_F, s_emptySigners, s_validTransmitters); - s_configDigestCommit = _getBasicConfigDigest(s_F, s_validSigners, s_validTransmitters); + s_configDigestExec = _getBasicConfigDigest(F, s_emptySigners, s_validTransmitters); + s_configDigestCommit = _getBasicConfigDigest(F, s_validSigners, s_validTransmitters); MultiOCR3Base.OCRConfigArgs[] memory ocrConfigs = new MultiOCR3Base.OCRConfigArgs[](2); ocrConfigs[0] = MultiOCR3Base.OCRConfigArgs({ ocrPluginType: uint8(Internal.OCRPluginType.Execution), configDigest: s_configDigestExec, - F: s_F, + F: F, isSignatureVerificationEnabled: false, signers: s_emptySigners, transmitters: s_validTransmitters @@ -95,7 +95,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { ocrConfigs[1] = MultiOCR3Base.OCRConfigArgs({ ocrPluginType: uint8(Internal.OCRPluginType.Commit), configDigest: s_configDigestCommit, - F: s_F, + F: F, isSignatureVerificationEnabled: true, signers: s_validSigners, transmitters: s_validTransmitters @@ -397,7 +397,7 @@ contract OffRampSetup is FeeQuoterSetup, MultiOCR3BaseSetup { bytes32[3] memory reportContext = [s_configDigestCommit, bytes32(uint256(sequenceNumber)), s_configDigestCommit]; (bytes32[] memory rs, bytes32[] memory ss,, bytes32 rawVs) = - _getSignaturesForDigest(s_validSignerKeys, abi.encode(commitReport), reportContext, s_F + 1); + _getSignaturesForDigest(s_validSignerKeys, abi.encode(commitReport), reportContext, F + 1); vm.startPrank(s_validTransmitters[0]); s_offRamp.commit(reportContext, abi.encode(commitReport), rs, ss, rawVs); diff --git a/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol b/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol index 5aea578f3d9..b2687063ea6 100644 --- a/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol +++ b/contracts/src/v0.8/ccip/test/onRamp/OnRamp.t.sol @@ -15,7 +15,8 @@ import {USDPriceWith18Decimals} from "../../libraries/USDPriceWith18Decimals.sol import {OnRamp} from "../../onRamp/OnRamp.sol"; import {TokenPool} from "../../pools/TokenPool.sol"; import {MaybeRevertingBurnMintTokenPool} from "../helpers/MaybeRevertingBurnMintTokenPool.sol"; -import "./OnRampSetup.t.sol"; +import {OnRampHelper} from "../helpers/OnRampHelper.sol"; +import {OnRampSetup} from "./OnRampSetup.t.sol"; import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; diff --git a/contracts/src/v0.8/ccip/test/pools/HybridLockReleaseUSDCTokenPool.t.sol b/contracts/src/v0.8/ccip/test/pools/HybridLockReleaseUSDCTokenPool.t.sol index 6f0d447f4d5..091db560b34 100644 --- a/contracts/src/v0.8/ccip/test/pools/HybridLockReleaseUSDCTokenPool.t.sol +++ b/contracts/src/v0.8/ccip/test/pools/HybridLockReleaseUSDCTokenPool.t.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.24; import {ILiquidityContainer} from "../../../liquiditymanager/interfaces/ILiquidityContainer.sol"; import {IBurnMintERC20} from "../../../shared/token/ERC20/IBurnMintERC20.sol"; -import {IPoolV1} from "../../interfaces/IPool.sol"; import {ITokenMessenger} from "../../pools/USDC/ITokenMessenger.sol"; import {BurnMintERC677} from "../../../shared/token/ERC677/BurnMintERC677.sol"; @@ -21,8 +20,6 @@ import {BaseTest} from "../BaseTest.t.sol"; import {MockE2EUSDCTransmitter} from "../mocks/MockE2EUSDCTransmitter.sol"; import {MockUSDCTokenMessenger} from "../mocks/MockUSDCTokenMessenger.sol"; -import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol"; - contract USDCTokenPoolSetup is BaseTest { IBurnMintERC20 internal s_token; MockUSDCTokenMessenger internal s_mockUSDC; @@ -61,7 +58,7 @@ contract USDCTokenPoolSetup is BaseTest { BurnMintERC677 usdcToken = new BurnMintERC677("LINK", "LNK", 18, 0); s_token = usdcToken; deal(address(s_token), OWNER, type(uint256).max); - setUpRamps(); + _setUpRamps(); s_mockUSDCTransmitter = new MockE2EUSDCTransmitter(0, DEST_DOMAIN_IDENTIFIER, address(s_token)); s_mockUSDC = new MockUSDCTokenMessenger(0, address(s_mockUSDCTransmitter)); @@ -114,7 +111,7 @@ contract USDCTokenPoolSetup is BaseTest { s_usdcTokenPool.setLiquidityProvider(SOURCE_CHAIN_SELECTOR, OWNER); } - function setUpRamps() internal { + function _setUpRamps() internal { s_router = new Router(address(s_token), address(s_mockRMN)); Router.OnRamp[] memory onRampUpdates = new Router.OnRamp[](1); diff --git a/contracts/src/v0.8/ccip/test/rateLimiter/MultiAggregateRateLimiter.t.sol b/contracts/src/v0.8/ccip/test/rateLimiter/MultiAggregateRateLimiter.t.sol index 42edde7e162..893656c9c8b 100644 --- a/contracts/src/v0.8/ccip/test/rateLimiter/MultiAggregateRateLimiter.t.sol +++ b/contracts/src/v0.8/ccip/test/rateLimiter/MultiAggregateRateLimiter.t.sol @@ -17,17 +17,17 @@ import {Vm} from "forge-std/Vm.sol"; contract MultiAggregateRateLimiterSetup is BaseTest, FeeQuoterSetup { MultiAggregateRateLimiterHelper internal s_rateLimiter; - address internal immutable TOKEN = 0x21118E64E1fB0c487F25Dd6d3601FF6af8D32E4e; + address internal constant TOKEN = 0x21118E64E1fB0c487F25Dd6d3601FF6af8D32E4e; uint224 internal constant TOKEN_PRICE = 4e18; uint64 internal constant CHAIN_SELECTOR_1 = 5009297550715157269; uint64 internal constant CHAIN_SELECTOR_2 = 4949039107694359620; - RateLimiter.Config internal RATE_LIMITER_CONFIG_1 = RateLimiter.Config({isEnabled: true, rate: 5, capacity: 100}); - RateLimiter.Config internal RATE_LIMITER_CONFIG_2 = RateLimiter.Config({isEnabled: true, rate: 10, capacity: 200}); + RateLimiter.Config internal s_rateLimiterConfig1 = RateLimiter.Config({isEnabled: true, rate: 5, capacity: 100}); + RateLimiter.Config internal s_rateLimiterConfig2 = RateLimiter.Config({isEnabled: true, rate: 10, capacity: 200}); - address internal immutable MOCK_OFFRAMP = address(1111); - address internal immutable MOCK_ONRAMP = address(1112); + address internal constant MOCK_OFFRAMP = address(1111); + address internal constant MOCK_ONRAMP = address(1112); address[] internal s_authorizedCallers; @@ -43,22 +43,22 @@ contract MultiAggregateRateLimiterSetup is BaseTest, FeeQuoterSetup { configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); configUpdates[1] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_2, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_2 + rateLimiterConfig: s_rateLimiterConfig2 }); configUpdates[2] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1, isOutboundLane: true, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); configUpdates[3] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_2, isOutboundLane: true, - rateLimiterConfig: RATE_LIMITER_CONFIG_2 + rateLimiterConfig: s_rateLimiterConfig2 }); s_authorizedCallers = new address[](2); @@ -169,32 +169,32 @@ contract MultiAggregateRateLimiter_setFeeQuoter is MultiAggregateRateLimiterSetu contract MultiAggregateRateLimiter_getTokenBucket is MultiAggregateRateLimiterSetup { function test_GetTokenBucket_Success() public view { RateLimiter.TokenBucket memory bucketInbound = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - _assertConfigWithTokenBucketEquality(RATE_LIMITER_CONFIG_1, bucketInbound); + _assertConfigWithTokenBucketEquality(s_rateLimiterConfig1, bucketInbound); assertEq(BLOCK_TIME, bucketInbound.lastUpdated); RateLimiter.TokenBucket memory bucketOutbound = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true); - _assertConfigWithTokenBucketEquality(RATE_LIMITER_CONFIG_1, bucketOutbound); + _assertConfigWithTokenBucketEquality(s_rateLimiterConfig1, bucketOutbound); assertEq(BLOCK_TIME, bucketOutbound.lastUpdated); } function test_Refill_Success() public { - RATE_LIMITER_CONFIG_1.capacity = RATE_LIMITER_CONFIG_1.capacity * 2; + s_rateLimiterConfig1.capacity = s_rateLimiterConfig1.capacity * 2; MultiAggregateRateLimiter.RateLimiterConfigArgs[] memory configUpdates = new MultiAggregateRateLimiter.RateLimiterConfigArgs[](1); configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); s_rateLimiter.applyRateLimiterConfigUpdates(configUpdates); RateLimiter.TokenBucket memory bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - assertEq(RATE_LIMITER_CONFIG_1.rate, bucket.rate); - assertEq(RATE_LIMITER_CONFIG_1.capacity, bucket.capacity); - assertEq(RATE_LIMITER_CONFIG_1.capacity / 2, bucket.tokens); + assertEq(s_rateLimiterConfig1.rate, bucket.rate); + assertEq(s_rateLimiterConfig1.capacity, bucket.capacity); + assertEq(s_rateLimiterConfig1.capacity / 2, bucket.tokens); assertEq(BLOCK_TIME, bucket.lastUpdated); uint256 warpTime = 4; @@ -202,16 +202,16 @@ contract MultiAggregateRateLimiter_getTokenBucket is MultiAggregateRateLimiterSe bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - assertEq(RATE_LIMITER_CONFIG_1.rate, bucket.rate); - assertEq(RATE_LIMITER_CONFIG_1.capacity, bucket.capacity); - assertEq(RATE_LIMITER_CONFIG_1.capacity / 2 + warpTime * RATE_LIMITER_CONFIG_1.rate, bucket.tokens); + assertEq(s_rateLimiterConfig1.rate, bucket.rate); + assertEq(s_rateLimiterConfig1.capacity, bucket.capacity); + assertEq(s_rateLimiterConfig1.capacity / 2 + warpTime * s_rateLimiterConfig1.rate, bucket.tokens); assertEq(BLOCK_TIME + warpTime, bucket.lastUpdated); vm.warp(BLOCK_TIME + warpTime * 100); // Bucket overflow bucket = s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, false); - assertEq(RATE_LIMITER_CONFIG_1.capacity, bucket.tokens); + assertEq(s_rateLimiterConfig1.capacity, bucket.tokens); } // Reverts @@ -242,7 +242,7 @@ contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggrega configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1 + 1, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); vm.expectEmit(); @@ -268,7 +268,7 @@ contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggrega configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1 + 1, isOutboundLane: true, - rateLimiterConfig: RATE_LIMITER_CONFIG_2 + rateLimiterConfig: s_rateLimiterConfig2 }); vm.expectEmit(); @@ -356,7 +356,7 @@ contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggrega configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_2 + rateLimiterConfig: s_rateLimiterConfig2 }); RateLimiter.TokenBucket memory bucket1 = @@ -382,7 +382,7 @@ contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggrega // Outbound lane config remains unchanged _assertConfigWithTokenBucketEquality( - RATE_LIMITER_CONFIG_1, s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true) + s_rateLimiterConfig1, s_rateLimiter.currentRateLimiterState(CHAIN_SELECTOR_1, true) ); } @@ -392,7 +392,7 @@ contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggrega configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); RateLimiter.TokenBucket memory bucketPreUpdate = @@ -420,7 +420,7 @@ contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggrega configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: 0, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); vm.expectRevert(MultiAggregateRateLimiter.ZeroChainSelectorNotAllowed.selector); @@ -433,7 +433,7 @@ contract MultiAggregateRateLimiter_applyRateLimiterConfigUpdates is MultiAggrega configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1 + 1, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); vm.startPrank(STRANGER); @@ -740,7 +740,7 @@ contract MultiAggregateRateLimiter_updateRateLimitTokens is MultiAggregateRateLi } contract MultiAggregateRateLimiter_onInboundMessage is MultiAggregateRateLimiterSetup { - address internal immutable MOCK_RECEIVER = address(1113); + address internal constant MOCK_RECEIVER = address(1113); function setUp() public virtual override { super.setUp(); @@ -815,7 +815,7 @@ contract MultiAggregateRateLimiter_onInboundMessage is MultiAggregateRateLimiter configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1, isOutboundLane: false, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); configUpdates[0].rateLimiterConfig.isEnabled = false; @@ -1050,7 +1050,7 @@ contract MultiAggregateRateLimiter_onOutboundMessage is MultiAggregateRateLimite configUpdates[0] = MultiAggregateRateLimiter.RateLimiterConfigArgs({ remoteChainSelector: CHAIN_SELECTOR_1, isOutboundLane: true, - rateLimiterConfig: RATE_LIMITER_CONFIG_1 + rateLimiterConfig: s_rateLimiterConfig1 }); configUpdates[0].rateLimiterConfig.isEnabled = false; diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNHome.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNHome.t.sol index 280f158da4d..449725317a1 100644 --- a/contracts/src/v0.8/ccip/test/rmn/RMNHome.t.sol +++ b/contracts/src/v0.8/ccip/test/rmn/RMNHome.t.sol @@ -2,10 +2,8 @@ pragma solidity 0.8.24; import {Ownable2Step} from "../../../shared/access/Ownable2Step.sol"; -import {Internal} from "../../libraries/Internal.sol"; import {RMNHome} from "../../rmn/RMNHome.sol"; import {Test} from "forge-std/Test.sol"; -import {Vm} from "forge-std/Vm.sol"; contract RMNHomeTest is Test { struct Config { diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemote.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemote.t.sol index 5f8f250b6e1..b9411d2e3a9 100644 --- a/contracts/src/v0.8/ccip/test/rmn/RMNRemote.t.sol +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemote.t.sol @@ -183,16 +183,16 @@ contract RMNRemote_curse is RMNRemoteSetup { s_rmnRemote.curse(s_curseSubjects); assertEq(abi.encode(s_rmnRemote.getCursedSubjects()), abi.encode(s_curseSubjects)); - assertTrue(s_rmnRemote.isCursed(curseSubj1)); - assertTrue(s_rmnRemote.isCursed(curseSubj2)); + assertTrue(s_rmnRemote.isCursed(CURSE_SUBJ_1)); + assertTrue(s_rmnRemote.isCursed(CURSE_SUBJ_2)); // Should not have cursed a random subject assertFalse(s_rmnRemote.isCursed(bytes16(keccak256("subject 3")))); } function test_curse_AlreadyCursed_duplicateSubject_reverts() public { - s_curseSubjects.push(curseSubj1); + s_curseSubjects.push(CURSE_SUBJ_1); - vm.expectRevert(abi.encodeWithSelector(RMNRemote.AlreadyCursed.selector, curseSubj1)); + vm.expectRevert(abi.encodeWithSelector(RMNRemote.AlreadyCursed.selector, CURSE_SUBJ_1)); s_rmnRemote.curse(s_curseSubjects); } @@ -217,14 +217,14 @@ contract RMNRemote_uncurse is RMNRemoteSetup { s_rmnRemote.uncurse(s_curseSubjects); assertEq(s_rmnRemote.getCursedSubjects().length, 0); - assertFalse(s_rmnRemote.isCursed(curseSubj1)); - assertFalse(s_rmnRemote.isCursed(curseSubj2)); + assertFalse(s_rmnRemote.isCursed(CURSE_SUBJ_1)); + assertFalse(s_rmnRemote.isCursed(CURSE_SUBJ_2)); } function test_uncurse_NotCursed_duplicatedUncurseSubject_reverts() public { - s_curseSubjects.push(curseSubj1); + s_curseSubjects.push(CURSE_SUBJ_1); - vm.expectRevert(abi.encodeWithSelector(RMNRemote.NotCursed.selector, curseSubj1)); + vm.expectRevert(abi.encodeWithSelector(RMNRemote.NotCursed.selector, CURSE_SUBJ_1)); s_rmnRemote.uncurse(s_curseSubjects); } diff --git a/contracts/src/v0.8/ccip/test/rmn/RMNRemoteSetup.t.sol b/contracts/src/v0.8/ccip/test/rmn/RMNRemoteSetup.t.sol index 435fa76cce0..88af28e992c 100644 --- a/contracts/src/v0.8/ccip/test/rmn/RMNRemoteSetup.t.sol +++ b/contracts/src/v0.8/ccip/test/rmn/RMNRemoteSetup.t.sol @@ -7,8 +7,6 @@ import {RMNRemote} from "../../rmn/RMNRemote.sol"; import {BaseTest} from "../BaseTest.t.sol"; import {Vm} from "forge-std/Vm.sol"; -import "forge-std/console.sol"; - contract RMNRemoteSetup is BaseTest { RMNRemote public s_rmnRemote; address public OFF_RAMP_ADDRESS; @@ -16,18 +14,18 @@ contract RMNRemoteSetup is BaseTest { RMNRemote.Signer[] public s_signers; Vm.Wallet[] public s_signerWallets; - Internal.MerkleRoot[] s_merkleRoots; - IRMNRemote.Signature[] s_signatures; + Internal.MerkleRoot[] internal s_merkleRoots; + IRMNRemote.Signature[] internal s_signatures; - bytes16 internal constant curseSubj1 = bytes16(keccak256("subject 1")); - bytes16 internal constant curseSubj2 = bytes16(keccak256("subject 2")); + bytes16 internal constant CURSE_SUBJ_1 = bytes16(keccak256("subject 1")); + bytes16 internal constant CURSE_SUBJ_2 = bytes16(keccak256("subject 2")); bytes16[] internal s_curseSubjects; function setUp() public virtual override { super.setUp(); s_rmnRemote = new RMNRemote(1); OFF_RAMP_ADDRESS = makeAddr("OFF RAMP"); - s_curseSubjects = [curseSubj1, curseSubj2]; + s_curseSubjects = [CURSE_SUBJ_1, CURSE_SUBJ_2]; _setupSigners(10); } @@ -46,13 +44,13 @@ contract RMNRemoteSetup is BaseTest { s_signers.pop(); } - for (uint256 i = 0; i < numSigners; i++) { + for (uint256 i = 0; i < numSigners; ++i) { s_signerWallets.push(vm.createWallet(_randomNum())); } _sort(s_signerWallets); - for (uint256 i = 0; i < numSigners; i++) { + for (uint256 i = 0; i < numSigners; ++i) { s_signers.push(RMNRemote.Signer({onchainPublicKey: s_signerWallets[i].addr, nodeIndex: uint64(i)})); } } @@ -60,8 +58,8 @@ contract RMNRemoteSetup is BaseTest { /// @notice generates n merkleRoots and matching valid signatures and populates them into /// the shared storage vars function _generatePayloadAndSigs(uint256 numUpdates, uint256 numSigs) internal { - require(numUpdates > 0, "need at least 1 dest lane update"); - require(numSigs <= s_signerWallets.length, "cannot generate more sigs than signers"); + vm.assertTrue(numUpdates > 0, "need at least 1 dest lane update"); + vm.assertTrue(numSigs <= s_signerWallets.length, "cannot generate more sigs than signers"); // remove any existing merkleRoots and sigs while (s_merkleRoots.length > 0) { diff --git a/contracts/src/v0.8/ccip/test/router/Router.t.sol b/contracts/src/v0.8/ccip/test/router/Router.t.sol index b5cfd6cfbec..a0fdfc3c2aa 100644 --- a/contracts/src/v0.8/ccip/test/router/Router.t.sol +++ b/contracts/src/v0.8/ccip/test/router/Router.t.sol @@ -11,7 +11,7 @@ import {Client} from "../../libraries/Client.sol"; import {Internal} from "../../libraries/Internal.sol"; import {OnRamp} from "../../onRamp/OnRamp.sol"; import {MaybeRevertMessageReceiver} from "../helpers/receivers/MaybeRevertMessageReceiver.sol"; -import {OffRampSetup} from "../offRamp/OffRampSetup.t.sol"; +import {OffRampSetup} from "../offRamp/offRamp/OffRampSetup.t.sol"; import {OnRampSetup} from "../onRamp/OnRampSetup.t.sol"; import {RouterSetup} from "../router/RouterSetup.t.sol"; diff --git a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom.t.sol b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom.t.sol index dfb599bd307..cf40fb62d22 100644 --- a/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom.t.sol +++ b/contracts/src/v0.8/ccip/test/tokenAdminRegistry/RegistryModuleOwnerCustom.t.sol @@ -8,6 +8,7 @@ import {RegistryModuleOwnerCustom} from "../../tokenAdminRegistry/RegistryModule import {TokenAdminRegistry} from "../../tokenAdminRegistry/TokenAdminRegistry.sol"; import {BurnMintERC677Helper} from "../helpers/BurnMintERC677Helper.sol"; +import {AccessControl} from "../../../vendor/openzeppelin-solidity/v5.0.2/contracts/access/AccessControl.sol"; import {Test} from "forge-std/Test.sol"; contract RegistryModuleOwnerCustomSetup is Test { @@ -102,3 +103,54 @@ contract RegistryModuleOwnerCustom_registerAdminViaOwner is RegistryModuleOwnerC s_registryModuleOwnerCustom.registerAdminViaOwner(s_token); } } + +contract AccessController is AccessControl { + constructor( + address admin + ) { + _grantRole(DEFAULT_ADMIN_ROLE, admin); + } +} + +contract RegistryModuleOwnerCustom_registerAccessControlDefaultAdmin is RegistryModuleOwnerCustomSetup { + function setUp() public override { + super.setUp(); + + s_token = address(new AccessController(OWNER)); + } + + function test_registerAccessControlDefaultAdmin_Success() public { + assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).administrator, address(0)); + + bytes32 defaultAdminRole = AccessController(s_token).DEFAULT_ADMIN_ROLE(); + + vm.expectCall(address(s_token), abi.encodeWithSelector(AccessControl.hasRole.selector, defaultAdminRole, OWNER), 1); + vm.expectCall( + address(s_tokenAdminRegistry), + abi.encodeWithSelector(TokenAdminRegistry.proposeAdministrator.selector, s_token, OWNER), + 1 + ); + + vm.expectEmit(); + emit RegistryModuleOwnerCustom.AdministratorRegistered(s_token, OWNER); + + s_registryModuleOwnerCustom.registerAccessControlDefaultAdmin(s_token); + + assertEq(s_tokenAdminRegistry.getTokenConfig(s_token).pendingAdministrator, OWNER); + } + + function test_registerAccessControlDefaultAdmin_Revert() public { + bytes32 defaultAdminRole = AccessController(s_token).DEFAULT_ADMIN_ROLE(); + + address wrongSender = makeAddr("Not_expected_owner"); + vm.startPrank(wrongSender); + + vm.expectRevert( + abi.encodeWithSelector( + RegistryModuleOwnerCustom.RequiredRoleNotFound.selector, wrongSender, defaultAdminRole, s_token + ) + ); + + s_registryModuleOwnerCustom.registerAccessControlDefaultAdmin(s_token); + } +} diff --git a/contracts/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol b/contracts/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol index dd2c82fe3dc..4392fa8c56f 100644 --- a/contracts/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol +++ b/contracts/src/v0.8/ccip/tokenAdminRegistry/RegistryModuleOwnerCustom.sol @@ -6,13 +6,16 @@ import {IGetCCIPAdmin} from "../interfaces/IGetCCIPAdmin.sol"; import {IOwner} from "../interfaces/IOwner.sol"; import {ITokenAdminRegistry} from "../interfaces/ITokenAdminRegistry.sol"; +import {AccessControl} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/access/AccessControl.sol"; + contract RegistryModuleOwnerCustom is ITypeAndVersion { error CanOnlySelfRegister(address admin, address token); + error RequiredRoleNotFound(address msgSender, bytes32 role, address token); error AddressZero(); event AdministratorRegistered(address indexed token, address indexed administrator); - string public constant override typeAndVersion = "RegistryModuleOwnerCustom 1.5.0"; + string public constant override typeAndVersion = "RegistryModuleOwnerCustom 1.6.0"; // The TokenAdminRegistry contract ITokenAdminRegistry internal immutable i_tokenAdminRegistry; @@ -44,6 +47,20 @@ contract RegistryModuleOwnerCustom is ITypeAndVersion { _registerAdmin(token, IOwner(token).owner()); } + /// @notice Registers the admin of the token using OZ's AccessControl DEFAULT_ADMIN_ROLE. + /// @param token The token to register the admin for. + /// @dev The caller must have the DEFAULT_ADMIN_ROLE as defined by the contract itself. + function registerAccessControlDefaultAdmin( + address token + ) external { + bytes32 defaultAdminRole = AccessControl(token).DEFAULT_ADMIN_ROLE(); + if (!AccessControl(token).hasRole(defaultAdminRole, msg.sender)) { + revert RequiredRoleNotFound(msg.sender, defaultAdminRole, token); + } + + _registerAdmin(token, msg.sender); + } + /// @notice Registers the admin of the token to msg.sender given that the /// admin is equal to msg.sender. /// @param token The token to register the admin for. diff --git a/core/capabilities/aggregator_factory.go b/core/capabilities/aggregator_factory.go index 1abf58b6c12..cfc1c96b0e9 100644 --- a/core/capabilities/aggregator_factory.go +++ b/core/capabilities/aggregator_factory.go @@ -18,6 +18,8 @@ func NewAggregator(name string, config values.Map, lggr logger.Logger) (types.Ag return datafeeds.NewDataFeedsAggregator(config, mc) case "identical": return aggregators.NewIdenticalAggregator(config) + case "reduce": + return aggregators.NewReduceAggregator(config) default: return nil, fmt.Errorf("aggregator %s not supported", name) } diff --git a/core/capabilities/ccip/launcher/deployment.go b/core/capabilities/ccip/launcher/deployment.go index 631c02d5cc9..64571183425 100644 --- a/core/capabilities/ccip/launcher/deployment.go +++ b/core/capabilities/ccip/launcher/deployment.go @@ -1,142 +1,81 @@ package launcher import ( + "errors" "fmt" + "sync" - cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "go.uber.org/multierr" - ccipreaderpkg "github.com/smartcontractkit/chainlink-ccip/pkg/reader" + cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" ) -// activeCandidateDeployment represents a active-candidate deployment of OCR instances. -type activeCandidateDeployment struct { - // active is the active OCR instance. - // active must always be present. - active cctypes.CCIPOracle - - // candidate is the candidate OCR instance. - // candidate may or may not be present. - // candidate must never be present if active is not present. - candidate cctypes.CCIPOracle -} - -// ccipDeployment represents active-candidate deployments of both commit and exec -// OCR instances. -type ccipDeployment struct { - commit activeCandidateDeployment - exec activeCandidateDeployment -} - -// Close shuts down all OCR instances in the deployment. -func (c *ccipDeployment) Close() error { - // we potentially run into this situation when - // trying to close an active instance that doesn't exist - // this check protects us from nil pointer exception - - if c == nil { - return nil - } - var err error - - // shutdown active commit instance. - if c.commit.active != nil { - err = multierr.Append(err, c.commit.active.Close()) - } - - // shutdown candidate commit instance. - if c.commit.candidate != nil { - err = multierr.Append(err, c.commit.candidate.Close()) - } - - // shutdown active exec instance. - if c.exec.active != nil { - err = multierr.Append(err, c.exec.active.Close()) - } +// MaxPlugins is the maximum number of plugins possible. +// A plugin represents a possible combination of (active/candidate) x (commit/exec) +// If we ever have more than 4 plugins in a prev or desired state, something went wrong +const MaxPlugins = 4 - // shutdown candidate exec instance. - if c.exec.candidate != nil { - err = multierr.Append(err, c.exec.candidate.Close()) - } +type pluginRegistry map[ocrtypes.ConfigDigest]cctypes.CCIPOracle - return err +// StartAll will call Oracle.Start on an entire don +func (c pluginRegistry) StartAll() error { + emptyPluginRegistry := make(pluginRegistry) + return c.TransitionFrom(emptyPluginRegistry) } -// StartActive starts the active OCR instances. -func (c *ccipDeployment) StartActive() error { - var err error - - err = multierr.Append(err, c.commit.active.Start()) - err = multierr.Append(err, c.exec.active.Start()) - - return err +// CloseAll is used to shut down an entire don immediately +func (c pluginRegistry) CloseAll() error { + emptyPluginRegistry := make(pluginRegistry) + return emptyPluginRegistry.TransitionFrom(c) } -// CloseActive shuts down the active OCR instances. -func (c *ccipDeployment) CloseActive() error { - var err error +// TransitionFrom manages starting and stopping ocr instances +// If there are any new config digests, we need to start those instances +// If any of the previous config digests are no longer present, we need to shut those down +// We don't care about if they're exec/commit or active/candidate, that all happens in the plugin +func (c pluginRegistry) TransitionFrom(prevPlugins pluginRegistry) error { + var allErrs error - err = multierr.Append(err, c.commit.active.Close()) - err = multierr.Append(err, c.exec.active.Close()) - - return err -} - -// TransitionDeployment handles the active-candidate deployment transition. -// prevDeployment is the previous deployment state. -// there are two possible cases: -// -// 1. both active and candidate are present in prevDeployment, but only active is present in c. -// this is a promotion of candidate to active, so we need to shut down the active deployment -// and make candidate the new active. In this case candidate is already running, so there's no -// need to start it. However, we need to shut down the active deployment. -// -// 2. only active is present in prevDeployment, both active and candidate are present in c. -// In this case, active is already running, so there's no need to start it. We need to -// start candidate. -func (c *ccipDeployment) TransitionDeployment(prevDeployment *ccipDeployment) error { - if prevDeployment == nil { - return fmt.Errorf("previous deployment is nil") + if len(c) > MaxPlugins || len(prevPlugins) > MaxPlugins { + return fmt.Errorf("current pluginRegistry or prevPlugins have more than 4 instances: len(prevPlugins): %d, len(currPlugins): %d", len(prevPlugins), len(c)) } - var err error - if prevDeployment.commit.candidate != nil && c.commit.candidate == nil { - err = multierr.Append(err, prevDeployment.commit.active.Close()) - } else if prevDeployment.commit.candidate == nil && c.commit.candidate != nil { - err = multierr.Append(err, c.commit.candidate.Start()) - } else { - return fmt.Errorf("invalid active-candidate deployment transition") + var wg sync.WaitGroup + var mu sync.Mutex + // This shuts down instances that were present previously, but are no longer needed + for digest, oracle := range prevPlugins { + if _, ok := c[digest]; !ok { + wg.Add(1) + go func(o cctypes.CCIPOracle) { + defer wg.Done() + if err := o.Close(); err != nil { + mu.Lock() + allErrs = multierr.Append(allErrs, err) + mu.Unlock() + } + }(oracle) + } } - - if prevDeployment.exec.candidate != nil && c.exec.candidate == nil { - err = multierr.Append(err, prevDeployment.exec.active.Close()) - } else if prevDeployment.exec.candidate == nil && c.exec.candidate != nil { - err = multierr.Append(err, c.exec.candidate.Start()) - } else { - return fmt.Errorf("invalid active-candidate deployment transition") + wg.Wait() + + // This will start the instances that were not previously present, but are in the new config + for digest, oracle := range c { + if digest == [32]byte{} { + allErrs = multierr.Append(allErrs, errors.New("cannot start a plugin with an empty config digest")) + } else if _, ok := prevPlugins[digest]; !ok { + wg.Add(1) + go func(o cctypes.CCIPOracle) { + defer wg.Done() + if err := o.Start(); err != nil { + mu.Lock() + allErrs = multierr.Append(allErrs, err) + mu.Unlock() + } + }(oracle) + } } + wg.Wait() - return err -} - -// HasCandidateInstance returns true if the deployment has a candidate instance for the -// given plugin type. -func (c *ccipDeployment) HasCandidateInstance(pluginType cctypes.PluginType) bool { - switch pluginType { - case cctypes.PluginTypeCCIPCommit: - return c.commit.candidate != nil - case cctypes.PluginTypeCCIPExec: - return c.exec.candidate != nil - default: - return false - } -} - -func isNewCandidateInstance(pluginType cctypes.PluginType, ocrConfigs []ccipreaderpkg.OCR3ConfigWithMeta, prevDeployment ccipDeployment) bool { - return len(ocrConfigs) == 2 && !prevDeployment.HasCandidateInstance(pluginType) -} - -func isPromotion(pluginType cctypes.PluginType, ocrConfigs []ccipreaderpkg.OCR3ConfigWithMeta, prevDeployment ccipDeployment) bool { - return len(ocrConfigs) == 1 && prevDeployment.HasCandidateInstance(pluginType) + return allErrs } diff --git a/core/capabilities/ccip/launcher/deployment_test.go b/core/capabilities/ccip/launcher/deployment_test.go index a7fa8888314..34b5dd6d18b 100644 --- a/core/capabilities/ccip/launcher/deployment_test.go +++ b/core/capabilities/ccip/launcher/deployment_test.go @@ -1,479 +1,292 @@ package launcher import ( - "errors" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "testing" - cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - mocktypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types/mocks" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/stretchr/testify/require" - ccipreaderpkg "github.com/smartcontractkit/chainlink-ccip/pkg/reader" + mocktypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types/mocks" ) -func Test_ccipDeployment_Close(t *testing.T) { +func Test_ccipDeployment_Transitions(t *testing.T) { + // we use a pointer to the oracle here for mock assertions type args struct { - commitBlue *mocktypes.CCIPOracle - commitGreen *mocktypes.CCIPOracle - execBlue *mocktypes.CCIPOracle - execGreen *mocktypes.CCIPOracle + prevDeployment map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle + currDeployment map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle + } + assertions := func(t *testing.T, args args) { + for i := range args.prevDeployment { + args.prevDeployment[i].AssertExpectations(t) + } + for i := range args.currDeployment { + args.currDeployment[i].AssertExpectations(t) + } } tests := []struct { - name string - args args - expect func(t *testing.T, args args) - asserts func(t *testing.T, args args) - wantErr bool + name string + makeArgs func(t *testing.T) args + expect func(t *testing.T, args args) + wantErr bool }{ { - name: "no errors, active only", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: nil, - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: nil, + name: "all plugins are new", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 4 { + prevP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 4 { + currP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + return args{prevDeployment: prevP, currDeployment: currP} }, expect: func(t *testing.T, args args) { - args.commitBlue.On("Close").Return(nil).Once() - args.execBlue.On("Close").Return(nil).Once() - }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.execBlue.AssertExpectations(t) + for _, plugin := range args.prevDeployment { + plugin.On("Close").Return(nil).Once() + } + for _, plugin := range args.currDeployment { + plugin.On("Start").Return(nil).Once() + } }, wantErr: false, }, { - name: "no errors, active and candidate", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), + name: "no configs -> candidates", + makeArgs: func(t *testing.T) args { + prev := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + + curr := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 2 { + curr[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + return args{prevDeployment: prev, currDeployment: curr} }, expect: func(t *testing.T, args args) { - args.commitBlue.On("Close").Return(nil).Once() - args.commitGreen.On("Close").Return(nil).Once() - args.execBlue.On("Close").Return(nil).Once() - args.execGreen.On("Close").Return(nil).Once() - }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.commitGreen.AssertExpectations(t) - args.execBlue.AssertExpectations(t) - args.execGreen.AssertExpectations(t) + // When we are creating candidates, they should be started + for _, plugin := range args.currDeployment { + plugin.On("Start").Return(nil).Once() + } }, wantErr: false, }, { - name: "error on commit active", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: nil, - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: nil, - }, - expect: func(t *testing.T, args args) { - args.commitBlue.On("Close").Return(errors.New("failed")).Once() - args.execBlue.On("Close").Return(nil).Once() - }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.execBlue.AssertExpectations(t) - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &ccipDeployment{ - commit: activeCandidateDeployment{ - active: tt.args.commitBlue, - }, - exec: activeCandidateDeployment{ - active: tt.args.execBlue, - }, - } - if tt.args.commitGreen != nil { - c.commit.candidate = tt.args.commitGreen - } - - if tt.args.execGreen != nil { - c.exec.candidate = tt.args.execGreen - } - - tt.expect(t, tt.args) - defer tt.asserts(t, tt.args) - err := c.Close() - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} + name: "candidates -> active", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 2 { + prevP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } -func Test_ccipDeployment_StartBlue(t *testing.T) { - type args struct { - commitBlue *mocktypes.CCIPOracle - execBlue *mocktypes.CCIPOracle - } - tests := []struct { - name string - args args - expect func(t *testing.T, args args) - asserts func(t *testing.T, args args) - wantErr bool - }{ - { - name: "no errors", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for digest, oracle := range prevP { + currP[digest] = oracle + } + return args{prevDeployment: prevP, currDeployment: currP} }, expect: func(t *testing.T, args args) { - args.commitBlue.On("Start").Return(nil).Once() - args.execBlue.On("Start").Return(nil).Once() - }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.execBlue.AssertExpectations(t) + // if candidates are being promoted, there should be nothing to start or stop + for _, plugin := range args.currDeployment { + plugin.AssertNotCalled(t, "Start") + plugin.AssertNotCalled(t, "Close") + } }, wantErr: false, }, { - name: "error on commit active", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), + name: "active -> active+candidates", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 2 { + prevP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for digest, oracle := range prevP { + currP[digest] = oracle + } + for range 2 { + currP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + return args{prevDeployment: prevP, currDeployment: currP} }, expect: func(t *testing.T, args args) { - args.commitBlue.On("Start").Return(errors.New("failed")).Once() - args.execBlue.On("Start").Return(nil).Once() - }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.execBlue.AssertExpectations(t) + for digest, plugin := range args.currDeployment { + // if it previously existed, there should be noop + // if it's a new instance, it should be started + if _, ok := args.prevDeployment[digest]; ok { + plugin.AssertNotCalled(t, "Start") + plugin.AssertNotCalled(t, "Close") + } else { + plugin.On("Start").Return(nil).Once() + } + } }, - wantErr: true, + wantErr: false, }, { - name: "error on exec active", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), + name: "active+candidate -> active", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 4 { + prevP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + // copy two digests over + i := 2 + for digest, oracle := range prevP { + if i == 0 { + continue + } + currP[digest] = oracle + i-- + } + return args{prevDeployment: prevP, currDeployment: currP} }, expect: func(t *testing.T, args args) { - args.commitBlue.On("Start").Return(nil).Once() - args.execBlue.On("Start").Return(errors.New("failed")).Once() + for digest, plugin := range args.prevDeployment { + if _, ok := args.currDeployment[digest]; !ok { + // if the instance is no longer present, it should have been deleted + plugin.On("Close").Return(nil).Once() + } else { + // otherwise, it should have been left alone + plugin.AssertNotCalled(t, "Close") + plugin.AssertNotCalled(t, "Start") + } + } }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.execBlue.AssertExpectations(t) - }, - wantErr: true, + wantErr: false, }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &ccipDeployment{ - commit: activeCandidateDeployment{ - active: tt.args.commitBlue, - }, - exec: activeCandidateDeployment{ - active: tt.args.execBlue, - }, - } + { + name: "candidate -> different candidate", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 2 { + prevP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } - tt.expect(t, tt.args) - defer tt.asserts(t, tt.args) - err := c.StartActive() - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 2 { + currP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } -func Test_ccipDeployment_CloseBlue(t *testing.T) { - type args struct { - commitBlue *mocktypes.CCIPOracle - execBlue *mocktypes.CCIPOracle - } - tests := []struct { - name string - args args - expect func(t *testing.T, args args) - asserts func(t *testing.T, args args) - wantErr bool - }{ - { - name: "no errors", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), + return args{prevDeployment: prevP, currDeployment: currP} }, expect: func(t *testing.T, args args) { - args.commitBlue.On("Close").Return(nil).Once() - args.execBlue.On("Close").Return(nil).Once() - }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.execBlue.AssertExpectations(t) + for _, plugin := range args.prevDeployment { + plugin.On("Close").Return(nil).Once() + } + for _, plugin := range args.currDeployment { + plugin.On("Start").Return(nil).Once() + } }, wantErr: false, }, { - name: "error on commit active", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), + name: "close all instances", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 4 { + prevP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + + return args{prevDeployment: prevP, currDeployment: currP} }, expect: func(t *testing.T, args args) { - args.commitBlue.On("Close").Return(errors.New("failed")).Once() - args.execBlue.On("Close").Return(nil).Once() - }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.execBlue.AssertExpectations(t) + for _, plugin := range args.prevDeployment { + plugin.On("Close").Return(nil).Once() + } }, - wantErr: true, + wantErr: false, }, { - name: "error on exec active", - args: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - }, - expect: func(t *testing.T, args args) { - args.commitBlue.On("Close").Return(nil).Once() - args.execBlue.On("Close").Return(errors.New("failed")).Once() - }, - asserts: func(t *testing.T, args args) { - args.commitBlue.AssertExpectations(t) - args.execBlue.AssertExpectations(t) - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &ccipDeployment{ - commit: activeCandidateDeployment{ - active: tt.args.commitBlue, - }, - exec: activeCandidateDeployment{ - active: tt.args.execBlue, - }, - } - - tt.expect(t, tt.args) - defer tt.asserts(t, tt.args) - err := c.CloseActive() - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) - } -} + name: "start all instances", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) -func Test_ccipDeployment_HandleBlueGreen_PrevDeploymentNil(t *testing.T) { - require.Error(t, (&ccipDeployment{}).TransitionDeployment(nil)) -} + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 4 { + currP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } -func Test_ccipDeployment_HandleBlueGreen(t *testing.T) { - type args struct { - commitBlue *mocktypes.CCIPOracle - commitGreen *mocktypes.CCIPOracle - execBlue *mocktypes.CCIPOracle - execGreen *mocktypes.CCIPOracle - } - tests := []struct { - name string - argsPrevDeployment args - argsFutureDeployment args - expect func(t *testing.T, args args, argsPrevDeployment args) - asserts func(t *testing.T, args args, argsPrevDeployment args) - wantErr bool - }{ - { - name: "promotion active to candidate", - argsPrevDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), + return args{prevDeployment: prevP, currDeployment: currP} }, - argsFutureDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: nil, - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: nil, - }, - expect: func(t *testing.T, args args, argsPrevDeployment args) { - argsPrevDeployment.commitBlue.On("Close").Return(nil).Once() - argsPrevDeployment.execBlue.On("Close").Return(nil).Once() - }, - asserts: func(t *testing.T, args args, argsPrevDeployment args) { - argsPrevDeployment.commitBlue.AssertExpectations(t) - argsPrevDeployment.execBlue.AssertExpectations(t) + expect: func(t *testing.T, args args) { + for _, plugin := range args.currDeployment { + plugin.On("Start").Return(nil).Once() + } }, wantErr: false, }, { - name: "new candidate deployment", - argsPrevDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: nil, - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: nil, + name: "should handle nil to nil", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + return args{prevDeployment: prevP, currDeployment: currP} }, - argsFutureDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), - }, - expect: func(t *testing.T, args args, argsPrevDeployment args) { - args.commitGreen.On("Start").Return(nil).Once() - args.execGreen.On("Start").Return(nil).Once() - }, - asserts: func(t *testing.T, args args, argsPrevDeployment args) { - args.commitGreen.AssertExpectations(t) - args.execGreen.AssertExpectations(t) + expect: func(t *testing.T, args args) { + for _, plugin := range args.currDeployment { + plugin.On("Start").Return(nil).Once() + } }, wantErr: false, }, { - name: "error on commit candidate start", - argsPrevDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: nil, - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: nil, - }, - argsFutureDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), - }, - expect: func(t *testing.T, args args, argsPrevDeployment args) { - args.commitGreen.On("Start").Return(errors.New("failed")).Once() - args.execGreen.On("Start").Return(nil).Once() - }, - asserts: func(t *testing.T, args args, argsPrevDeployment args) { - args.commitGreen.AssertExpectations(t) - args.execGreen.AssertExpectations(t) - }, + name: "should throw error if there are more than 5 instances", + makeArgs: func(t *testing.T) args { + prevP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + currP := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 5 { + currP[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + return args{prevDeployment: prevP, currDeployment: currP} + }, + expect: func(t *testing.T, args args) {}, wantErr: true, }, { - name: "error on exec candidate start", - argsPrevDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: nil, - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: nil, - }, - argsFutureDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), - }, - expect: func(t *testing.T, args args, argsPrevDeployment args) { - args.commitGreen.On("Start").Return(nil).Once() - args.execGreen.On("Start").Return(errors.New("failed")).Once() - }, - asserts: func(t *testing.T, args args, argsPrevDeployment args) { - args.commitGreen.AssertExpectations(t) - args.execGreen.AssertExpectations(t) - }, - wantErr: true, - }, - { - name: "invalid active-candidate deployment transition commit: both prev and future deployment have candidate", - argsPrevDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), - }, - argsFutureDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), - }, - expect: func(t *testing.T, args args, argsPrevDeployment args) {}, - asserts: func(t *testing.T, args args, argsPrevDeployment args) {}, - wantErr: true, - }, - { - name: "invalid active-candidate deployment transition exec: both prev and future exec deployment have candidate", - argsPrevDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: nil, - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), - }, - argsFutureDeployment: args{ - commitBlue: mocktypes.NewCCIPOracle(t), - commitGreen: mocktypes.NewCCIPOracle(t), - execBlue: mocktypes.NewCCIPOracle(t), - execGreen: mocktypes.NewCCIPOracle(t), - }, - expect: func(t *testing.T, args args, argsPrevDeployment args) { - args.commitGreen.On("Start").Return(nil).Once() + name: "candidate -> init", + makeArgs: func(t *testing.T) args { + prev := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + for range 2 { + prev[utils.RandomBytes32()] = mocktypes.NewCCIPOracle(t) + } + curr := make(map[ocrtypes.ConfigDigest]*mocktypes.CCIPOracle) + + return args{prevDeployment: prev, currDeployment: curr} }, - asserts: func(t *testing.T, args args, argsPrevDeployment args) { - args.commitGreen.AssertExpectations(t) + expect: func(t *testing.T, args args) { + // When we are creating candidates, they should be started + for _, plugin := range args.prevDeployment { + plugin.On("Close").Return(nil).Once() + } }, - wantErr: true, + wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - futDeployment := &ccipDeployment{ - commit: activeCandidateDeployment{ - active: tt.argsFutureDeployment.commitBlue, - }, - exec: activeCandidateDeployment{ - active: tt.argsFutureDeployment.execBlue, - }, + args := tt.makeArgs(t) + prev := make(pluginRegistry) + for digest, oracle := range args.prevDeployment { + prev[digest] = oracle } - if tt.argsFutureDeployment.commitGreen != nil { - futDeployment.commit.candidate = tt.argsFutureDeployment.commitGreen - } - if tt.argsFutureDeployment.execGreen != nil { - futDeployment.exec.candidate = tt.argsFutureDeployment.execGreen - } - - prevDeployment := &ccipDeployment{ - commit: activeCandidateDeployment{ - active: tt.argsPrevDeployment.commitBlue, - }, - exec: activeCandidateDeployment{ - active: tt.argsPrevDeployment.execBlue, - }, + curr := make(pluginRegistry) + for digest, oracle := range args.currDeployment { + curr[digest] = oracle } - if tt.argsPrevDeployment.commitGreen != nil { - prevDeployment.commit.candidate = tt.argsPrevDeployment.commitGreen - } - if tt.argsPrevDeployment.execGreen != nil { - prevDeployment.exec.candidate = tt.argsPrevDeployment.execGreen - } - - tt.expect(t, tt.argsFutureDeployment, tt.argsPrevDeployment) - defer tt.asserts(t, tt.argsFutureDeployment, tt.argsPrevDeployment) - err := futDeployment.TransitionDeployment(prevDeployment) + tt.expect(t, args) + defer assertions(t, args) + err := curr.TransitionFrom(prev) if tt.wantErr { require.Error(t, err) } else { @@ -482,200 +295,3 @@ func Test_ccipDeployment_HandleBlueGreen(t *testing.T) { }) } } - -func Test_isNewGreenInstance(t *testing.T) { - type args struct { - pluginType cctypes.PluginType - ocrConfigs []ccipreaderpkg.OCR3ConfigWithMeta - prevDeployment ccipDeployment - } - tests := []struct { - name string - args args - want bool - }{ - { - "prev deployment only active", - args{ - pluginType: cctypes.PluginTypeCCIPCommit, - ocrConfigs: []ccipreaderpkg.OCR3ConfigWithMeta{ - {}, {}, - }, - prevDeployment: ccipDeployment{ - commit: activeCandidateDeployment{ - active: mocktypes.NewCCIPOracle(t), - }, - }, - }, - true, - }, - { - "candidate -> active promotion", - args{ - pluginType: cctypes.PluginTypeCCIPCommit, - ocrConfigs: []ccipreaderpkg.OCR3ConfigWithMeta{ - {}, - }, - prevDeployment: ccipDeployment{ - commit: activeCandidateDeployment{ - active: mocktypes.NewCCIPOracle(t), - candidate: mocktypes.NewCCIPOracle(t), - }, - }, - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := isNewCandidateInstance(tt.args.pluginType, tt.args.ocrConfigs, tt.args.prevDeployment) - require.Equal(t, tt.want, got) - }) - } -} - -func Test_isPromotion(t *testing.T) { - type args struct { - pluginType cctypes.PluginType - ocrConfigs []ccipreaderpkg.OCR3ConfigWithMeta - prevDeployment ccipDeployment - } - tests := []struct { - name string - args args - want bool - }{ - { - "prev deployment only active", - args{ - pluginType: cctypes.PluginTypeCCIPCommit, - ocrConfigs: []ccipreaderpkg.OCR3ConfigWithMeta{ - {}, {}, - }, - prevDeployment: ccipDeployment{ - commit: activeCandidateDeployment{ - active: mocktypes.NewCCIPOracle(t), - }, - }, - }, - false, - }, - { - "candidate -> active promotion", - args{ - pluginType: cctypes.PluginTypeCCIPCommit, - ocrConfigs: []ccipreaderpkg.OCR3ConfigWithMeta{ - {}, - }, - prevDeployment: ccipDeployment{ - commit: activeCandidateDeployment{ - active: mocktypes.NewCCIPOracle(t), - candidate: mocktypes.NewCCIPOracle(t), - }, - }, - }, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := isPromotion(tt.args.pluginType, tt.args.ocrConfigs, tt.args.prevDeployment); got != tt.want { - t.Errorf("isPromotion() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ccipDeployment_HasGreenInstance(t *testing.T) { - type fields struct { - commit activeCandidateDeployment - exec activeCandidateDeployment - } - type args struct { - pluginType cctypes.PluginType - } - tests := []struct { - name string - fields fields - args args - want bool - }{ - { - "commit candidate present", - fields{ - commit: activeCandidateDeployment{ - active: mocktypes.NewCCIPOracle(t), - candidate: mocktypes.NewCCIPOracle(t), - }, - }, - args{ - pluginType: cctypes.PluginTypeCCIPCommit, - }, - true, - }, - { - "commit candidate not present", - fields{ - commit: activeCandidateDeployment{ - active: mocktypes.NewCCIPOracle(t), - }, - }, - args{ - pluginType: cctypes.PluginTypeCCIPCommit, - }, - false, - }, - { - "exec candidate present", - fields{ - exec: activeCandidateDeployment{ - active: mocktypes.NewCCIPOracle(t), - candidate: mocktypes.NewCCIPOracle(t), - }, - }, - args{ - pluginType: cctypes.PluginTypeCCIPExec, - }, - true, - }, - { - "exec candidate not present", - fields{ - exec: activeCandidateDeployment{ - active: mocktypes.NewCCIPOracle(t), - }, - }, - args{ - pluginType: cctypes.PluginTypeCCIPExec, - }, - false, - }, - { - "invalid plugin type", - fields{}, - args{ - pluginType: cctypes.PluginType(100), - }, - false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &ccipDeployment{} - if tt.fields.commit.active != nil { - c.commit.active = tt.fields.commit.active - } - if tt.fields.commit.candidate != nil { - c.commit.candidate = tt.fields.commit.candidate - } - if tt.fields.exec.active != nil { - c.exec.active = tt.fields.exec.active - } - if tt.fields.exec.candidate != nil { - c.exec.candidate = tt.fields.exec.candidate - } - got := c.HasCandidateInstance(tt.args.pluginType) - require.Equal(t, tt.want, got) - }) - } -} diff --git a/core/capabilities/ccip/launcher/launcher.go b/core/capabilities/ccip/launcher/launcher.go index c52c6493181..167ac0a815e 100644 --- a/core/capabilities/ccip/launcher/launcher.go +++ b/core/capabilities/ccip/launcher/launcher.go @@ -2,12 +2,14 @@ package launcher import ( "context" - "errors" "fmt" "sync" "time" + ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/job" p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" @@ -48,9 +50,9 @@ func New( IDsToNodes: make(map[p2ptypes.PeerID]kcr.INodeInfoProviderNodeInfo), IDsToCapabilities: make(map[string]registrysyncer.Capability), }, - dons: make(map[registrysyncer.DonID]*ccipDeployment), tickInterval: tickInterval, oracleCreator: oracleCreator, + instances: make(map[registrysyncer.DonID]pluginRegistry), } } @@ -76,10 +78,10 @@ type launcher struct { wg sync.WaitGroup tickInterval time.Duration - // dons is a map of CCIP DON IDs to the OCR instances that are running on them. - // we can have up to two OCR instances per CCIP plugin, since we are running two plugins, - // thats four OCR instances per CCIP DON maximum. - dons map[registrysyncer.DonID]*ccipDeployment + // instances is a map of CCIP DON IDs to a map of the OCR instances that are running on them. + // This map uses the config digest as the key, and the instance as the value. + // We can have up to a maximum of 4 instances per CCIP DON (active/candidate) x (commit/exec) + instances map[registrysyncer.DonID]pluginRegistry } // Launch implements registrysyncer.Launcher. @@ -101,7 +103,7 @@ func (l *launcher) runningDONIDs() []registrysyncer.DonID { l.lock.RLock() defer l.lock.RUnlock() var runningDONs []registrysyncer.DonID - for id := range l.dons { + for id := range l.instances { runningDONs = append(runningDONs, id) } return runningDONs @@ -116,8 +118,8 @@ func (l *launcher) Close() error { // shut down all running oracles. var err error - for _, ceDep := range l.dons { - err = multierr.Append(err, ceDep.Close()) + for _, ceDep := range l.instances { + err = multierr.Append(err, ceDep.CloseAll()) } return err @@ -189,195 +191,180 @@ func (l *launcher) processDiff(diff diffResult) error { return err } +// processUpdate will manage when configurations of an existing don are updated +// If new oracles are needed, they are created and started. Old ones will be shut down func (l *launcher) processUpdate(updated map[registrysyncer.DonID]registrysyncer.DON) error { l.lock.Lock() defer l.lock.Unlock() for donID, don := range updated { - prevDeployment, ok := l.dons[registrysyncer.DonID(don.ID)] + prevPlugins, ok := l.instances[donID] if !ok { return fmt.Errorf("invariant violation: expected to find CCIP DON %d in the map of running deployments", don.ID) } - // we encounter this when a node is removed from the don - if prevDeployment == nil { - return errors.New("this node was closed") + + latestConfigs, err := getConfigsForDon(l.homeChainReader, don) + if err != nil { + return err } - futDeployment, err := updateDON( + newPlugins, err := updateDON( l.lggr, l.myP2PID, - l.homeChainReader, - *prevDeployment, + prevPlugins, don, l.oracleCreator, - ) + latestConfigs) if err != nil { return err } - // When we remove a node from the don, this node does not have a future deployment - if futDeployment != nil { - if err := futDeployment.TransitionDeployment(prevDeployment); err != nil { - // TODO: how to handle a failed active-candidate deployment? - return fmt.Errorf("failed to handle active-candidate deployment for CCIP DON %d: %w", donID, err) - } - // update state. - l.dons[donID] = futDeployment - // update the state with the latest config. - // this way if one of the starts errors, we don't retry all of them. - l.regState.IDsToDONs[donID] = updated[donID] + err = newPlugins.TransitionFrom(prevPlugins) + if err != nil { + return fmt.Errorf("could not transition state %w", err) } + + l.instances[donID] = newPlugins + l.regState.IDsToDONs[donID] = updated[donID] } return nil } +// processAdded is for when a new don is created. We know that all oracles +// must be created and started func (l *launcher) processAdded(added map[registrysyncer.DonID]registrysyncer.DON) error { l.lock.Lock() defer l.lock.Unlock() for donID, don := range added { - dep, err := createDON( + configs, err := getConfigsForDon(l.homeChainReader, don) + if err != nil { + return fmt.Errorf("failed to get current configs for don %d: %w", donID, err) + } + newPlugins, err := createDON( l.lggr, l.myP2PID, - l.homeChainReader, don, l.oracleCreator, + configs, ) if err != nil { return fmt.Errorf("processAdded: call createDON %d: %w", donID, err) } - if dep == nil { + if len(newPlugins) == 0 { // not a member of this DON. continue } - // TODO: this doesn't seem to be correct; a newly added DON will not have an active - // instance but a candidate instance. - if err := dep.StartActive(); err != nil { - if shutdownErr := dep.CloseActive(); shutdownErr != nil { - l.lggr.Errorw("Failed to shutdown active instance after failed start", "donId", donID, "err", shutdownErr) + // now that oracles are created, we need to start them. If there are issues with starting + // we should shut them down + if err := newPlugins.StartAll(); err != nil { + if shutdownErr := newPlugins.CloseAll(); shutdownErr != nil { + l.lggr.Errorw("Failed to shutdown don instances after a failed start", "donId", donID, "err", shutdownErr) } return fmt.Errorf("processAdded: start oracles for CCIP DON %d: %w", donID, err) } // update state. - l.dons[donID] = dep - // update the state with the latest config. - // this way if one of the starts errors, we don't retry all of them. + l.instances[donID] = newPlugins l.regState.IDsToDONs[donID] = added[donID] } return nil } +// processRemoved handles the situation when an entire DON is removed func (l *launcher) processRemoved(removed map[registrysyncer.DonID]registrysyncer.DON) error { l.lock.Lock() defer l.lock.Unlock() for id := range removed { - ceDep, ok := l.dons[id] + p, ok := l.instances[id] if !ok { // not running this particular DON. continue } - if err := ceDep.Close(); err != nil { + if err := p.CloseAll(); err != nil { return fmt.Errorf("failed to shutdown oracles for CCIP DON %d: %w", id, err) + } // after a successful shutdown we can safely remove the DON deployment from the map. - delete(l.dons, id) + delete(l.instances, id) delete(l.regState.IDsToDONs, id) } return nil } -// updateDON is a pure function that handles the case where a DON in the capability registry -// has received a new configuration. -// It returns a new ccipDeployment that can then be used to perform the active-candidate deployment, -// based on the previous deployment. func updateDON( lggr logger.Logger, p2pID ragep2ptypes.PeerID, - homeChainReader ccipreader.HomeChain, - prevDeployment ccipDeployment, + prevPlugins pluginRegistry, don registrysyncer.DON, oracleCreator cctypes.OracleCreator, -) (futDeployment *ccipDeployment, err error) { + latestConfigs []ccipreader.OCR3ConfigWithMeta, +) (pluginRegistry, error) { if !isMemberOfDON(don, p2pID) { lggr.Infow("Not a member of this DON, skipping", "donId", don.ID, "p2pId", p2pID.String()) - return nil, nil } - // this should be a retryable error. - commitOCRConfigs, err := homeChainReader.GetOCRConfigs(context.Background(), don.ID, uint8(cctypes.PluginTypeCCIPCommit)) - if err != nil { - return nil, fmt.Errorf("failed to fetch OCR configs for CCIP commit plugin (don id: %d) from home chain config contract: %w", - don.ID, err) - } - - execOCRConfigs, err := homeChainReader.GetOCRConfigs(context.Background(), don.ID, uint8(cctypes.PluginTypeCCIPExec)) - if err != nil { - return nil, fmt.Errorf("failed to fetch OCR configs for CCIP exec plugin (don id: %d) from home chain config contract: %w", - don.ID, err) - } - - commitAcd, err := createFutureActiveCandidateDeployment(don.ID, prevDeployment, commitOCRConfigs, oracleCreator, cctypes.PluginTypeCCIPCommit) - if err != nil { - return nil, fmt.Errorf("failed to create future active-candidate deployment for CCIP commit plugin: %w, don id: %d", err, don.ID) - } + newP := make(pluginRegistry) + // If a config digest is not already in our list, we need to create an oracle + // If a config digest is already in our list, we just need to point to the old one + // newP.Transition will make sure we shut down the old oracles, and start the new ones + for _, c := range latestConfigs { + digest := c.ConfigDigest + if _, ok := prevPlugins[digest]; !ok { + oracle, err := oracleCreator.Create(don.ID, cctypes.OCR3ConfigWithMeta(c)) + if err != nil { + return nil, fmt.Errorf("failed to create CCIP oracle: %w for digest %x", err, digest) + } - execAcd, err := createFutureActiveCandidateDeployment(don.ID, prevDeployment, execOCRConfigs, oracleCreator, cctypes.PluginTypeCCIPExec) - if err != nil { - return nil, fmt.Errorf("failed to create future active-candidate deployment for CCIP exec plugin: %w, don id: %d", err, don.ID) + newP[digest] = oracle + } else { + newP[digest] = prevPlugins[digest] + } } - return &ccipDeployment{ - commit: commitAcd, - exec: execAcd, - }, nil + return newP, nil } -// TODO: this is not technically correct, CCIPHome has other transitions -// that are not covered here, e.g revokeCandidate. -func createFutureActiveCandidateDeployment( - donID uint32, - prevDeployment ccipDeployment, - ocrConfigs []ccipreader.OCR3ConfigWithMeta, +// createDON is a pure function that handles the case where a new DON is added to the capability registry. +// It returns up to 4 plugins that are later started. +func createDON( + lggr logger.Logger, + p2pID ragep2ptypes.PeerID, + don registrysyncer.DON, oracleCreator cctypes.OracleCreator, - pluginType cctypes.PluginType, -) (activeCandidateDeployment, error) { - var deployment activeCandidateDeployment - if isNewCandidateInstance(pluginType, ocrConfigs, prevDeployment) { - // this is a new candidate instance. - candidateOracle, err := oracleCreator.Create(donID, cctypes.OCR3ConfigWithMeta(ocrConfigs[1])) + configs []ccipreader.OCR3ConfigWithMeta, +) (pluginRegistry, error) { + if !isMemberOfDON(don, p2pID) && oracleCreator.Type() == cctypes.OracleTypePlugin { + lggr.Infow("Not a member of this DON and not a bootstrap node either, skipping", "donId", don.ID, "p2pId", p2pID.String()) + return nil, nil + } + p := make(pluginRegistry) + for _, config := range configs { + digest, err := ocrtypes.BytesToConfigDigest(config.ConfigDigest[:]) if err != nil { - return activeCandidateDeployment{}, fmt.Errorf("failed to create CCIP commit oracle: %w", err) + return nil, fmt.Errorf("digest does not match type %w", err) } - deployment.active = prevDeployment.commit.active - deployment.candidate = candidateOracle - } else if isPromotion(pluginType, ocrConfigs, prevDeployment) { - // this is a promotion of candidate->active. - deployment.active = prevDeployment.commit.candidate - } else { - return activeCandidateDeployment{}, fmt.Errorf("invariant violation: expected 1 or 2 OCR configs for CCIP plugin (type: %d), got %d", pluginType, len(ocrConfigs)) - } + oracle, err := oracleCreator.Create(don.ID, cctypes.OCR3ConfigWithMeta(config)) + if err != nil { + return nil, fmt.Errorf("failed to create CCIP oracle: %w for digest %x", err, digest) + } - return deployment, nil + p[digest] = oracle + } + return p, nil } -// createDON is a pure function that handles the case where a new DON is added to the capability registry. -// It returns a new ccipDeployment that can then be used to start the active instance. -func createDON( - lggr logger.Logger, - p2pID ragep2ptypes.PeerID, +func getConfigsForDon( homeChainReader ccipreader.HomeChain, - don registrysyncer.DON, - oracleCreator cctypes.OracleCreator, -) (*ccipDeployment, error) { + don registrysyncer.DON) ([]ccipreader.OCR3ConfigWithMeta, error) { // this should be a retryable error. commitOCRConfigs, err := homeChainReader.GetOCRConfigs(context.Background(), don.ID, uint8(cctypes.PluginTypeCCIPCommit)) if err != nil { @@ -391,39 +378,19 @@ func createDON( don.ID, err) } - // upon creation we should only have one OCR config per plugin type. - if len(commitOCRConfigs) != 1 { - return nil, fmt.Errorf("expected exactly one OCR config for CCIP commit plugin (don id: %d), got %d", don.ID, len(commitOCRConfigs)) - } - - if len(execOCRConfigs) != 1 { - return nil, fmt.Errorf("expected exactly one OCR config for CCIP exec plugin (don id: %d), got %d", don.ID, len(execOCRConfigs)) - } - - if !isMemberOfDON(don, p2pID) && oracleCreator.Type() == cctypes.OracleTypePlugin { - lggr.Infow("Not a member of this DON and not a bootstrap node either, skipping", "donId", don.ID, "p2pId", p2pID.String()) - return nil, nil - } - - // at this point we know we are either a member of the DON or a bootstrap node. - // the injected oracleCreator will create the appropriate oracle. - commitOracle, err := oracleCreator.Create(don.ID, cctypes.OCR3ConfigWithMeta(commitOCRConfigs[0])) - if err != nil { - return nil, fmt.Errorf("failed to create CCIP commit oracle: %w", err) + c := []ccipreader.OCR3ConfigWithMeta{ + commitOCRConfigs.CandidateConfig, + commitOCRConfigs.ActiveConfig, + execOCRConfigs.CandidateConfig, + execOCRConfigs.ActiveConfig, } - execOracle, err := oracleCreator.Create(don.ID, cctypes.OCR3ConfigWithMeta(execOCRConfigs[0])) - if err != nil { - return nil, fmt.Errorf("failed to create CCIP exec oracle: %w", err) + ret := make([]ccipreader.OCR3ConfigWithMeta, 0, 4) + for _, config := range c { + if config.ConfigDigest != [32]byte{} { + ret = append(ret, config) + } } - // TODO: incorrect, should be setting candidate? - return &ccipDeployment{ - commit: activeCandidateDeployment{ - active: commitOracle, - }, - exec: activeCandidateDeployment{ - active: execOracle, - }, - }, nil + return ret, nil } diff --git a/core/capabilities/ccip/launcher/launcher_test.go b/core/capabilities/ccip/launcher/launcher_test.go index bb3bb8a843a..188ee48c215 100644 --- a/core/capabilities/ccip/launcher/launcher_test.go +++ b/core/capabilities/ccip/launcher/launcher_test.go @@ -2,9 +2,10 @@ package launcher import ( "math/big" - "reflect" "testing" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types/mocks" @@ -48,20 +49,28 @@ func Test_createDON(t *testing.T) { func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) { homeChainReader. On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPCommit)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPCommit), - Nodes: getOCR3Nodes(3, 4), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) homeChainReader. On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPExec)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPExec), - Nodes: getOCR3Nodes(3, 4), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) oracleCreator.EXPECT().Type().Return(cctypes.OracleTypePlugin).Once() }, false, @@ -81,20 +90,28 @@ func Test_createDON(t *testing.T) { func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) { homeChainReader. On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPCommit)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPCommit), - Nodes: getOCR3Nodes(3, 4), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) homeChainReader. On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPExec)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPExec), - Nodes: getOCR3Nodes(3, 4), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) oracleCreator.EXPECT().Type().Return(cctypes.OracleTypeBootstrap).Once() oracleCreator.EXPECT().Create(mock.Anything, mock.Anything).Return(mocks.NewCCIPOracle(t), nil).Twice() }, @@ -112,20 +129,29 @@ func Test_createDON(t *testing.T) { func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) { homeChainReader. On("GetOCRConfigs", mock.Anything, uint32(1), uint8(cctypes.PluginTypeCCIPCommit)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPCommit), - Nodes: getOCR3Nodes(3, 4), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) + homeChainReader. On("GetOCRConfigs", mock.Anything, uint32(1), uint8(cctypes.PluginTypeCCIPExec)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPExec), - Nodes: getOCR3Nodes(3, 4), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPCommit) @@ -138,6 +164,65 @@ func Test_createDON(t *testing.T) { }, false, }, + { + "if a don is created with active and candidate configs, all should be created", + args{ + logger.TestLogger(t), + p2pID1, + mocks.NewHomeChainReader(t), + mocks.NewOracleCreator(t), + defaultRegistryDon, + }, + func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) { + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(1), uint8(cctypes.PluginTypeCCIPCommit)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + }, nil) + + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(1), uint8(cctypes.PluginTypeCCIPExec)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + }, nil) + + oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { + return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPCommit) + })). + Return(mocks.NewCCIPOracle(t), nil).Twice() + oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { + return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPExec) + })). + Return(mocks.NewCCIPOracle(t), nil).Twice() + }, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -145,7 +230,9 @@ func Test_createDON(t *testing.T) { tt.expect(t, tt.args, tt.args.oracleCreator, tt.args.homeChainReader) } - _, err := createDON(tt.args.lggr, tt.args.p2pID, tt.args.homeChainReader, tt.args.don, tt.args.oracleCreator) + latestConfigs, err := getConfigsForDon(tt.args.homeChainReader, tt.args.don) + require.NoError(t, err) + _, err = createDON(tt.args.lggr, tt.args.p2pID, tt.args.don, tt.args.oracleCreator, latestConfigs) if tt.wantErr { require.Error(t, err) } else { @@ -155,74 +242,280 @@ func Test_createDON(t *testing.T) { } } -func Test_createFutureBlueGreenDeployment(t *testing.T) { - type args struct { - donID uint32 - prevDeployment ccipDeployment - ocrConfigs []ccipreaderpkg.OCR3ConfigWithMeta - oracleCreator *mocks.OracleCreator - pluginType cctypes.PluginType - } - tests := []struct { - name string - args args - want activeCandidateDeployment - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := createFutureActiveCandidateDeployment(tt.args.donID, tt.args.prevDeployment, tt.args.ocrConfigs, tt.args.oracleCreator, tt.args.pluginType) - if (err != nil) != tt.wantErr { - t.Errorf("createFutureActiveCandidateDeployment() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("createFutureActiveCandidateDeployment() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_updateDON(t *testing.T) { + var ( + digest1 = utils.RandomBytes32() + digest2 = utils.RandomBytes32() + ) type args struct { lggr logger.Logger p2pID ragep2ptypes.PeerID homeChainReader *mocks.HomeChainReader oracleCreator *mocks.OracleCreator - prevDeployment ccipDeployment don registrysyncer.DON + prevPlugins pluginRegistry } tests := []struct { - name string - args args - wantFutDeployment *ccipDeployment - wantErr bool + name string + args args + desiredLen int + expect func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) + wantErr bool }{ - // TODO: Add test cases. + { + name: "should start new plugins", + args: args{ + logger.TestLogger(t), + p2pID3, + mocks.NewHomeChainReader(t), + mocks.NewOracleCreator(t), + registrysyncer.DON{ + DON: getDON(2, []ragep2ptypes.PeerID{p2pID3, p2pID4}, 0), + CapabilityConfigurations: defaultCapCfgs, + }, + pluginRegistry{ + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + }, + }, + desiredLen: 2, + expect: func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) { + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPCommit)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + }, nil) + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPExec)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + }, nil) + oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { + return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPCommit) + })). + Return(mocks.NewCCIPOracle(t), nil) + oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { + return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPExec) + })). + Return(mocks.NewCCIPOracle(t), nil) + }, + wantErr: false, + }, + { + name: "should return no plugins if config is empty", + args: args{ + logger.TestLogger(t), + p2pID3, + mocks.NewHomeChainReader(t), + mocks.NewOracleCreator(t), + registrysyncer.DON{ + DON: getDON(2, []ragep2ptypes.PeerID{p2pID3, p2pID4}, 0), + CapabilityConfigurations: defaultCapCfgs, + }, + pluginRegistry{ + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + }, + }, + desiredLen: 0, + expect: func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) { + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPCommit)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + }, nil) + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPExec)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + }, nil) + oracleCreator.AssertNotCalled(t, "Create") + }, + wantErr: false, + }, + { + name: "should maintain existing plugins", + args: args{ + logger.TestLogger(t), + p2pID3, + mocks.NewHomeChainReader(t), + mocks.NewOracleCreator(t), + registrysyncer.DON{ + DON: getDON(2, []ragep2ptypes.PeerID{p2pID3, p2pID4}, 0), + CapabilityConfigurations: defaultCapCfgs, + }, + pluginRegistry{ + digest1: mocks.NewCCIPOracle(t), + digest2: mocks.NewCCIPOracle(t), + }, + }, + desiredLen: 4, + expect: func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) { + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPCommit)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: digest1, + }, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + }, nil) + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPExec)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: digest2, + }, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + }, nil) + oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { + return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPCommit) + })). + Return(mocks.NewCCIPOracle(t), nil).Once() + oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { + return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPExec) + })). + Return(mocks.NewCCIPOracle(t), nil).Once() + }, + wantErr: false, + }, + { + name: "should start brand new plugins if all are new", + args: args{ + logger.TestLogger(t), + p2pID3, + mocks.NewHomeChainReader(t), + mocks.NewOracleCreator(t), + registrysyncer.DON{ + DON: getDON(2, []ragep2ptypes.PeerID{p2pID3, p2pID4}, 0), + CapabilityConfigurations: defaultCapCfgs, + }, + pluginRegistry{ + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + utils.RandomBytes32(): mocks.NewCCIPOracle(t), + }, + }, + desiredLen: 4, + expect: func(t *testing.T, args args, oracleCreator *mocks.OracleCreator, homeChainReader *mocks.HomeChainReader) { + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPCommit)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + }, nil) + homeChainReader. + On("GetOCRConfigs", mock.Anything, uint32(2), uint8(cctypes.PluginTypeCCIPExec)). + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), + }, + }, nil) + oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { + return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPCommit) + })). + Return(mocks.NewCCIPOracle(t), nil).Twice() + oracleCreator.EXPECT().Create(mock.Anything, mock.MatchedBy(func(cfg cctypes.OCR3ConfigWithMeta) bool { + return cfg.Config.PluginType == uint8(cctypes.PluginTypeCCIPExec) + })). + Return(mocks.NewCCIPOracle(t), nil).Twice() + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotFutDeployment, err := updateDON(tt.args.lggr, tt.args.p2pID, tt.args.homeChainReader, tt.args.prevDeployment, tt.args.don, tt.args.oracleCreator) + if tt.expect != nil { + tt.expect(t, tt.args, tt.args.oracleCreator, tt.args.homeChainReader) + } + + latestConfigs, err := getConfigsForDon(tt.args.homeChainReader, tt.args.don) + require.NoError(t, err) + newPlugins, err := updateDON(tt.args.lggr, tt.args.p2pID, tt.args.prevPlugins, tt.args.don, tt.args.oracleCreator, latestConfigs) if (err != nil) != tt.wantErr { t.Errorf("updateDON() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(gotFutDeployment, tt.wantFutDeployment) { - t.Errorf("updateDON() = %v, want %v", gotFutDeployment, tt.wantFutDeployment) + + if len(newPlugins) != tt.desiredLen { + t.Errorf("updateDON() error. Wanted new length of plugins to be %d, got %d", tt.desiredLen, len(newPlugins)) } }) } } func Test_launcher_processDiff(t *testing.T) { + var ( + digest1 = utils.RandomBytes32() + digest2 = utils.RandomBytes32() + ) type fields struct { lggr logger.Logger p2pID ragep2ptypes.PeerID homeChainReader *mocks.HomeChainReader oracleCreator *mocks.OracleCreator - dons map[registrysyncer.DonID]*ccipDeployment + instances map[registrysyncer.DonID]pluginRegistry regState registrysyncer.LocalRegistry } type args struct { @@ -238,22 +531,18 @@ func Test_launcher_processDiff(t *testing.T) { { "don removed success", fields{ - dons: map[registrysyncer.DonID]*ccipDeployment{ + instances: map[registrysyncer.DonID]pluginRegistry{ 1: { - commit: activeCandidateDeployment{ - active: newMock(t, - func(t *testing.T) *mocks.CCIPOracle { return mocks.NewCCIPOracle(t) }, - func(m *mocks.CCIPOracle) { - m.On("Close").Return(nil) - }), - }, - exec: activeCandidateDeployment{ - active: newMock(t, - func(t *testing.T) *mocks.CCIPOracle { return mocks.NewCCIPOracle(t) }, - func(m *mocks.CCIPOracle) { - m.On("Close").Return(nil) - }), - }, + utils.RandomBytes32(): newMock(t, + func(t *testing.T) *mocks.CCIPOracle { return mocks.NewCCIPOracle(t) }, + func(m *mocks.CCIPOracle) { + m.On("Close").Return(nil).Once() + }), + utils.RandomBytes32(): newMock(t, + func(t *testing.T) *mocks.CCIPOracle { return mocks.NewCCIPOracle(t) }, + func(m *mocks.CCIPOracle) { + m.On("Close").Return(nil).Once() + }), }, }, regState: registrysyncer.LocalRegistry{ @@ -270,8 +559,8 @@ func Test_launcher_processDiff(t *testing.T) { }, }, func(t *testing.T, l *launcher) { - require.Len(t, l.dons, 0) - require.Len(t, l.regState.IDsToDONs, 0) + require.Empty(t, l.instances) + require.Empty(t, l.regState.IDsToDONs) }, false, }, @@ -284,17 +573,27 @@ func Test_launcher_processDiff(t *testing.T) { return mocks.NewHomeChainReader(t) }, func(m *mocks.HomeChainReader) { m.On("GetOCRConfigs", mock.Anything, uint32(1), uint8(cctypes.PluginTypeCCIPCommit)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) m.On("GetOCRConfigs", mock.Anything, uint32(1), uint8(cctypes.PluginTypeCCIPExec)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPExec), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{}, + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) }), oracleCreator: newMock(t, func(t *testing.T) *mocks.OracleCreator { return mocks.NewOracleCreator(t) @@ -312,7 +611,7 @@ func Test_launcher_processDiff(t *testing.T) { })). Return(execOracle, nil) }), - dons: map[registrysyncer.DonID]*ccipDeployment{}, + instances: map[registrysyncer.DonID]pluginRegistry{}, regState: registrysyncer.LocalRegistry{ IDsToDONs: map[registrysyncer.DonID]registrysyncer.DON{}, }, @@ -325,7 +624,7 @@ func Test_launcher_processDiff(t *testing.T) { }, }, func(t *testing.T, l *launcher) { - require.Len(t, l.dons, 1) + require.Len(t, l.instances, 1) require.Len(t, l.regState.IDsToDONs, 1) }, false, @@ -339,25 +638,39 @@ func Test_launcher_processDiff(t *testing.T) { return mocks.NewHomeChainReader(t) }, func(m *mocks.HomeChainReader) { m.On("GetOCRConfigs", mock.Anything, uint32(1), uint8(cctypes.PluginTypeCCIPCommit)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }, { - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPCommit), + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPCommit), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: digest1, }, - }}, nil) + }, nil) m.On("GetOCRConfigs", mock.Anything, uint32(1), uint8(cctypes.PluginTypeCCIPExec)). - Return([]ccipreaderpkg.OCR3ConfigWithMeta{{ - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPExec), + Return(ccipreaderpkg.ActiveAndCandidate{ + ActiveConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: digest2, }, - }, { - Config: ccipreaderpkg.OCR3Config{ - PluginType: uint8(cctypes.PluginTypeCCIPExec), + CandidateConfig: ccipreaderpkg.OCR3ConfigWithMeta{ + Config: ccipreaderpkg.OCR3Config{ + PluginType: uint8(cctypes.PluginTypeCCIPExec), + Nodes: getOCR3Nodes(3, 4), + }, + ConfigDigest: utils.RandomBytes32(), }, - }}, nil) + }, nil) }), oracleCreator: newMock(t, func(t *testing.T) *mocks.OracleCreator { return mocks.NewOracleCreator(t) @@ -375,18 +688,14 @@ func Test_launcher_processDiff(t *testing.T) { })). Return(execOracle, nil) }), - dons: map[registrysyncer.DonID]*ccipDeployment{ + instances: map[registrysyncer.DonID]pluginRegistry{ 1: { - commit: activeCandidateDeployment{ - active: newMock(t, func(t *testing.T) *mocks.CCIPOracle { - return mocks.NewCCIPOracle(t) - }, func(m *mocks.CCIPOracle) {}), - }, - exec: activeCandidateDeployment{ - active: newMock(t, func(t *testing.T) *mocks.CCIPOracle { - return mocks.NewCCIPOracle(t) - }, func(m *mocks.CCIPOracle) {}), - }, + digest1: newMock(t, func(t *testing.T) *mocks.CCIPOracle { + return mocks.NewCCIPOracle(t) + }, func(m *mocks.CCIPOracle) {}), + digest2: newMock(t, func(t *testing.T) *mocks.CCIPOracle { + return mocks.NewCCIPOracle(t) + }, func(m *mocks.CCIPOracle) {}), }, }, regState: registrysyncer.LocalRegistry{ @@ -407,7 +716,7 @@ func Test_launcher_processDiff(t *testing.T) { }, }, func(t *testing.T, l *launcher) { - require.Len(t, l.dons, 1) + require.Len(t, l.instances, 1) require.Len(t, l.regState.IDsToDONs, 1) require.Len(t, l.regState.IDsToDONs[1].Members, 2) }, @@ -417,7 +726,7 @@ func Test_launcher_processDiff(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { l := &launcher{ - dons: tt.fields.dons, + instances: tt.fields.instances, regState: tt.fields.regState, myP2PID: tt.fields.p2pID, lggr: tt.fields.lggr, diff --git a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go index fa40dca6b85..7c8d61def80 100644 --- a/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go +++ b/core/capabilities/ccip/ocrimpls/contract_transmitter_test.go @@ -415,7 +415,7 @@ func makeTestEvmTxm( keyStore keystore.Eth) (txmgr.TxManager, gas.EvmFeeEstimator) { config, dbConfig, evmConfig := MakeTestConfigs(t) - estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config.ChainType(), evmConfig.GasEstimator()) + estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) require.NoError(t, err, "failed to create gas estimator") lggr := logger.TestLogger(t) diff --git a/core/capabilities/ccip/types/mocks/home_chain_reader.go b/core/capabilities/ccip/types/mocks/home_chain_reader.go index 7aa857d46cb..95fc20d226e 100644 --- a/core/capabilities/ccip/types/mocks/home_chain_reader.go +++ b/core/capabilities/ccip/types/mocks/home_chain_reader.go @@ -65,23 +65,23 @@ func (_m *HomeChainReader) Name() string { } // GetOCRConfigs provides a mock function with given fields: ctx, donID, pluginType -func (_m *HomeChainReader) GetOCRConfigs(ctx context.Context, donID uint32, pluginType uint8) ([]ccipreaderpkg.OCR3ConfigWithMeta, error) { +func (_m *HomeChainReader) GetOCRConfigs(ctx context.Context, donID uint32, pluginType uint8) (ccipreaderpkg.ActiveAndCandidate, error) { ret := _m.Called(ctx, donID, pluginType) if len(ret) == 0 { panic("no return value specified for GetOCRConfigs") } - var r0 []ccipreaderpkg.OCR3ConfigWithMeta + var r0 ccipreaderpkg.ActiveAndCandidate var r1 error - if rf, ok := ret.Get(0).(func(ctx context.Context, donID uint32, pluginType uint8) ([]ccipreaderpkg.OCR3ConfigWithMeta, error)); ok { + if rf, ok := ret.Get(0).(func(ctx context.Context, donID uint32, pluginType uint8) (ccipreaderpkg.ActiveAndCandidate, error)); ok { return rf(ctx, donID, pluginType) } - if rf, ok := ret.Get(0).(func(ctx context.Context, donID uint32, pluginType uint8) []ccipreaderpkg.OCR3ConfigWithMeta); ok { + if rf, ok := ret.Get(0).(func(ctx context.Context, donID uint32, pluginType uint8) ccipreaderpkg.ActiveAndCandidate); ok { r0 = rf(ctx, donID, pluginType) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]ccipreaderpkg.OCR3ConfigWithMeta) + r0 = ret.Get(0).(ccipreaderpkg.ActiveAndCandidate) } } diff --git a/core/capabilities/gateway_connector/service_wrapper.go b/core/capabilities/gateway_connector/service_wrapper.go index 824c92b4f89..7aa9efd59b3 100644 --- a/core/capabilities/gateway_connector/service_wrapper.go +++ b/core/capabilities/gateway_connector/service_wrapper.go @@ -55,7 +55,7 @@ func NewGatewayConnectorServiceWrapper(config config.GatewayConnector, keystore config: config, keystore: keystore, clock: clock, - lggr: lggr, + lggr: lggr.Named("GatewayConnectorServiceWrapper"), } } @@ -106,7 +106,7 @@ func (e *ServiceWrapper) HealthReport() map[string]error { } func (e *ServiceWrapper) Name() string { - return "GatewayConnectorServiceWrapper" + return e.lggr.Name() } func (e *ServiceWrapper) GetGatewayConnector() connector.GatewayConnector { diff --git a/core/capabilities/launcher.go b/core/capabilities/launcher.go index 1d309816c1c..ef0dd19bd40 100644 --- a/core/capabilities/launcher.go +++ b/core/capabilities/launcher.go @@ -124,7 +124,7 @@ func (w *launcher) HealthReport() map[string]error { } func (w *launcher) Name() string { - return "CapabilitiesLauncher" + return w.lggr.Name() } func (w *launcher) Launch(ctx context.Context, state *registrysyncer.LocalRegistry) error { diff --git a/core/capabilities/remote/dispatcher.go b/core/capabilities/remote/dispatcher.go index f27d691bb66..e3229d35c1e 100644 --- a/core/capabilities/remote/dispatcher.go +++ b/core/capabilities/remote/dispatcher.go @@ -236,5 +236,5 @@ func (d *dispatcher) HealthReport() map[string]error { } func (d *dispatcher) Name() string { - return "Dispatcher" + return d.lggr.Name() } diff --git a/core/capabilities/remote/target/client.go b/core/capabilities/remote/target/client.go index 64dcbd14f01..8249c40bb01 100644 --- a/core/capabilities/remote/target/client.go +++ b/core/capabilities/remote/target/client.go @@ -202,5 +202,5 @@ func (c *client) HealthReport() map[string]error { } func (c *client) Name() string { - return "TargetClient" + return c.lggr.Name() } diff --git a/core/capabilities/remote/target/server.go b/core/capabilities/remote/target/server.go index 5324475b192..61725a8220c 100644 --- a/core/capabilities/remote/target/server.go +++ b/core/capabilities/remote/target/server.go @@ -226,5 +226,5 @@ func (r *server) HealthReport() map[string]error { } func (r *server) Name() string { - return "TargetServer" + return r.lggr.Name() } diff --git a/core/capabilities/remote/trigger_publisher.go b/core/capabilities/remote/trigger_publisher.go index e5a46c87914..315959605e8 100644 --- a/core/capabilities/remote/trigger_publisher.go +++ b/core/capabilities/remote/trigger_publisher.go @@ -333,5 +333,5 @@ func (p *triggerPublisher) HealthReport() map[string]error { } func (p *triggerPublisher) Name() string { - return "TriggerPublisher" + return p.lggr.Name() } diff --git a/core/capabilities/remote/trigger_subscriber.go b/core/capabilities/remote/trigger_subscriber.go index 9f40c6c1f51..2638d9ca5f3 100644 --- a/core/capabilities/remote/trigger_subscriber.go +++ b/core/capabilities/remote/trigger_subscriber.go @@ -3,7 +3,7 @@ package remote import ( "context" "errors" - sync "sync" + "sync" "time" commoncap "github.com/smartcontractkit/chainlink-common/pkg/capabilities" @@ -269,5 +269,5 @@ func (s *triggerSubscriber) HealthReport() map[string]error { } func (s *triggerSubscriber) Name() string { - return "TriggerSubscriber" + return s.lggr.Name() } diff --git a/core/capabilities/webapi/trigger/trigger.go b/core/capabilities/webapi/trigger/trigger.go index a08d2f577ff..c607f0dbb6f 100644 --- a/core/capabilities/webapi/trigger/trigger.go +++ b/core/capabilities/webapi/trigger/trigger.go @@ -265,7 +265,7 @@ func (h *triggerConnectorHandler) HealthReport() map[string]error { } func (h *triggerConnectorHandler) Name() string { - return "WebAPITrigger" + return h.lggr.Name() } func (h *triggerConnectorHandler) sendResponse(ctx context.Context, gatewayID string, requestBody *api.MessageBody, payload any) error { diff --git a/core/chains/evm/config/toml/config.go b/core/chains/evm/config/toml/config.go index 69011d0025a..0505449943e 100644 --- a/core/chains/evm/config/toml/config.go +++ b/core/chains/evm/config/toml/config.go @@ -815,6 +815,7 @@ func (d *DAOracle) setFrom(f *DAOracle) { if v := f.OracleType; v != nil { d.OracleType = v } + if v := f.OracleAddress; v != nil { d.OracleAddress = v } diff --git a/core/chains/evm/gas/models.go b/core/chains/evm/gas/models.go index a162cdd014a..6a7b7c1a1ef 100644 --- a/core/chains/evm/gas/models.go +++ b/core/chains/evm/gas/models.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" pkgerrors "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" bigmath "github.com/smartcontractkit/chainlink-common/pkg/utils/big_math" @@ -54,7 +53,7 @@ type feeEstimatorClient interface { } // NewEstimator returns the estimator for a given config -func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype chaintype.ChainType, geCfg evmconfig.GasEstimator) (EvmFeeEstimator, error) { +func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype chaintype.ChainType, geCfg evmconfig.GasEstimator, clientsByChainID map[string]rollups.DAClient) (EvmFeeEstimator, error) { bh := geCfg.BlockHistory() s := geCfg.Mode() lggr.Infow(fmt.Sprintf("Initializing EVM gas estimator in mode: %s", s), @@ -82,14 +81,11 @@ func NewEstimator(lggr logger.Logger, ethClient feeEstimatorClient, chaintype ch df := geCfg.EIP1559DynamicFees() // create l1Oracle only if it is supported for the chain - var l1Oracle rollups.L1Oracle - if rollups.IsRollupWithL1Support(chaintype) { - var err error - l1Oracle, err = rollups.NewL1GasOracle(lggr, ethClient, chaintype, geCfg.DAOracle()) - if err != nil { - return nil, fmt.Errorf("failed to initialize L1 oracle: %w", err) - } + l1Oracle, err := rollups.NewL1GasOracle(lggr, ethClient, chaintype, geCfg.DAOracle(), clientsByChainID) + if err != nil { + return nil, fmt.Errorf("failed to initialize L1 oracle: %w", err) } + var newEstimator func(logger.Logger) EvmEstimator switch s { case "Arbitrum": diff --git a/core/chains/evm/gas/models_test.go b/core/chains/evm/gas/models_test.go index 936a4810569..3cc83ec7034 100644 --- a/core/chains/evm/gas/models_test.go +++ b/core/chains/evm/gas/models_test.go @@ -72,8 +72,9 @@ func TestWrappedEvmEstimator(t *testing.T) { assert.Nil(t, l1Oracle) // expect l1Oracle + daOracle := rollups.CreateTestDAOracle(t, toml.DAOracleOPStack, "0x420000000000000000000000000000000000000F", "") - oracle, err := rollups.NewL1GasOracle(lggr, nil, chaintype.ChainOptimismBedrock, daOracle) + oracle, err := rollups.NewL1GasOracle(lggr, nil, chaintype.ChainOptimismBedrock, daOracle, nil) require.NoError(t, err) // cast oracle to L1Oracle interface estimator = gas.NewEvmFeeEstimator(lggr, getEst, false, geCfg, nil) diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index 6eb965dc160..18c326ab8f6 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/rpc" - "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -34,6 +33,12 @@ type l1OracleClient interface { BatchCallContext(ctx context.Context, b []rpc.BatchElem) error } +// DAClient is interface of client connections for additional chains layers +type DAClient interface { + SuggestGasPrice(ctx context.Context) (*big.Int, error) + FeeHistory(ctx context.Context, blockCount uint64, rewardPercentiles []float64) (feeHistory *ethereum.FeeHistory, err error) +} + type priceEntry struct { price *assets.Wei timestamp time.Time @@ -50,13 +55,20 @@ func IsRollupWithL1Support(chainType chaintype.ChainType) bool { return slices.Contains(supportedChainTypes, chainType) } -func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, daOracle evmconfig.DAOracle) (L1Oracle, error) { +func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType, daOracle evmconfig.DAOracle, clientsByChainID map[string]DAClient) (L1Oracle, error) { if !IsRollupWithL1Support(chainType) { return nil, nil } + var l1Oracle L1Oracle var err error + + // TODO(CCIP-3551) the actual usage of the clientsByChainID should update the check accordingly, potentially return errors instead of logging. Going forward all configs should specify a DAOracle config. This is a fall back to maintain backwards compat. if daOracle != nil { + if clientsByChainID == nil { + lggr.Debugf("clientsByChainID map is missing") + } + oracleType := daOracle.OracleType() if oracleType == nil { return nil, errors.New("required field OracleType is nil in non-nil DAOracle config") @@ -80,7 +92,6 @@ func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chai } } - // Going forward all configs should specify a DAOracle config. This is a fall back to maintain backwards compat. switch chainType { case chaintype.ChainArbitrum: l1Oracle, err = NewArbitrumL1GasOracle(lggr, ethClient) diff --git a/core/chains/evm/gas/rollups/l1_oracle_test.go b/core/chains/evm/gas/rollups/l1_oracle_test.go index 5c8dac1b4f6..9c36878a153 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/l1_oracle_test.go @@ -31,7 +31,7 @@ func TestL1Oracle(t *testing.T) { ethClient := mocks.NewL1OracleClient(t) daOracle := CreateTestDAOracle(t, toml.DAOracleOPStack, utils.RandomAddress().String(), "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainCelo, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainCelo, daOracle, nil) require.NoError(t, err) assert.Nil(t, oracle) }) @@ -39,7 +39,7 @@ func TestL1Oracle(t *testing.T) { t.Run("DAOracle config is nil, falls back to using chainType", func(t *testing.T) { ethClient := mocks.NewL1OracleClient(t) - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum, nil) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum, nil, nil) require.NoError(t, err) assert.NotNil(t, oracle) }) @@ -48,7 +48,7 @@ func TestL1Oracle(t *testing.T) { ethClient := mocks.NewL1OracleClient(t) daOracle := CreateTestDAOracle(t, "", utils.RandomAddress().String(), "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum, daOracle, nil) require.NoError(t, err) assert.NotNil(t, oracle) assert.Equal(t, oracle.Name(), "L1GasOracle(arbitrum)") @@ -58,7 +58,7 @@ func TestL1Oracle(t *testing.T) { ethClient := mocks.NewL1OracleClient(t) daOracle := CreateTestDAOracle(t, "", utils.RandomAddress().String(), "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainZkSync, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainZkSync, daOracle, nil) require.NoError(t, err) assert.NotNil(t, oracle) assert.Equal(t, oracle.Name(), "L1GasOracle(zkSync)") @@ -72,7 +72,7 @@ func TestL1Oracle_GasPrice(t *testing.T) { ethClient := mocks.NewL1OracleClient(t) daOracle := CreateTestDAOracle(t, toml.DAOracleOPStack, utils.RandomAddress().String(), "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle, nil) require.NoError(t, err) _, err = oracle.GasPrice(tests.Context(t)) @@ -96,7 +96,7 @@ func TestL1Oracle_GasPrice(t *testing.T) { }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) daOracle := CreateTestDAOracle(t, toml.DAOracleArbitrum, "0x0000000000000000000000000000000000000000", "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainArbitrum, daOracle, nil) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -126,7 +126,7 @@ func TestL1Oracle_GasPrice(t *testing.T) { }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) daOracle := CreateTestDAOracle(t, toml.DAOracleOPStack, oracleAddress, "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainKroma, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainKroma, daOracle, nil) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -156,7 +156,7 @@ func TestL1Oracle_GasPrice(t *testing.T) { }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) daOracle := CreateTestDAOracle(t, toml.DAOracleOPStack, oracleAddress, "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainOptimismBedrock, daOracle, nil) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -185,7 +185,7 @@ func TestL1Oracle_GasPrice(t *testing.T) { }).Return(common.BigToHash(l1BaseFee).Bytes(), nil) daOracle := CreateTestDAOracle(t, toml.DAOracleOPStack, oracleAddress, "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainScroll, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainScroll, daOracle, nil) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -223,7 +223,7 @@ func TestL1Oracle_GasPrice(t *testing.T) { }).Return(common.BigToHash(gasPerPubByteL2).Bytes(), nil) daOracle := CreateTestDAOracle(t, toml.DAOracleZKSync, utils.RandomAddress().String(), "") - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainZkSync, daOracle) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainZkSync, daOracle, nil) require.NoError(t, err) servicetest.RunHealthy(t, oracle) @@ -250,7 +250,7 @@ func TestL1Oracle_GasPrice(t *testing.T) { // chainType here shouldn't matter for now since we're checking daOracleConfig oracle type first. Later // we can consider limiting the chainType to only those that support custom calldata. - oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainZkSync, daOracleConfig) + oracle, err := NewL1GasOracle(logger.Test(t), ethClient, chaintype.ChainZkSync, daOracleConfig, nil) require.NoError(t, err) servicetest.RunHealthy(t, oracle) diff --git a/core/chains/evm/gas/rollups/op_l1_oracle_test.go b/core/chains/evm/gas/rollups/op_l1_oracle_test.go index 0b67cc7178d..75450d72522 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -288,7 +288,6 @@ func TestOPL1Oracle_CalculateFjordGasPrice(t *testing.T) { blobBaseFeeScalar := big.NewInt(5) decimals := big.NewInt(6) oracleAddress := utils.RandomAddress().String() - t.Parallel() t.Run("correctly fetches gas price if chain has upgraded to Fjord", func(t *testing.T) { diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index bafe46fefc5..5b54373dfc6 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -91,7 +91,7 @@ func TestEthBroadcaster_Lifecycle(t *testing.T) { estimator := gasmocks.NewEvmFeeEstimator(t) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator) txmClient := txmgr.NewEvmTxmClient(ethClient, nil) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Twice() eb := txmgr.NewEvmBroadcaster( txStore, txmClient, @@ -149,7 +149,7 @@ func TestEthBroadcaster_LoadNextSequenceMapFailure_StartupSuccess(t *testing.T) cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) estimator := gasmocks.NewEvmFeeEstimator(t) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), errors.New("Getting on-chain nonce failed")) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), errors.New("Getting on-chain nonce failed")).Once() txmClient := txmgr.NewEvmTxmClient(ethClient, nil) eb := txmgr.NewEvmBroadcaster( txStore, @@ -185,8 +185,8 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) checkerFactory := &txmgr.CheckerFactory{Client: ethClient} - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - ethClient.On("PendingNonceAt", mock.Anything, otherAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, otherAddress, mock.Anything).Return(uint64(0), nil).Once() lggr := logger.Test(t) nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, checkerFactory, false, nonceTracker) @@ -387,7 +387,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { c.EVM[0].GasEstimator.PriceMax = assets.NewWeiI(rnd + 2) }) evmcfg = evmtest.NewChainScopedConfig(t, cfg) - ethClient.On("PendingNonceAt", mock.Anything, otherAddress).Return(uint64(1), nil).Once() + ethClient.On("NonceAt", mock.Anything, otherAddress, mock.Anything).Return(uint64(1), nil).Once() nonceTracker = txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb = NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, checkerFactory, false, nonceTracker) @@ -556,7 +556,7 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) evmcfg := evmtest.NewChainScopedConfig(t, cfg) checkerFactory := &testCheckerFactory{} - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, checkerFactory, false, nonceTracker) @@ -648,7 +648,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi close(chStartEstimate) <-chBlock }).Once() - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() txmClient := txmgr.NewEvmTxmClient(ethClient, nil) eb := txmgr.NewEvmBroadcaster( txStore, @@ -706,7 +706,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success_WithMultiplier(t *testing evmcfg := evmtest.NewChainScopedConfig(t, cfg) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, &testCheckerFactory{}, false, nonceTracker) @@ -788,7 +788,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { _, fromAddress := cltest.RandomKey{Nonce: nextNonce.Int64()}.MustInsertWithState(t, ethKeyStore) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, &testCheckerFactory{}, false, nonceTracker) @@ -827,7 +827,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { _, fromAddress := cltest.RandomKey{Nonce: nextNonce.Int64()}.MustInsertWithState(t, ethKeyStore) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, &testCheckerFactory{}, false, nonceTracker) @@ -864,7 +864,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { _, fromAddress := cltest.RandomKey{Nonce: nextNonce.Int64()}.MustInsertWithState(t, ethKeyStore) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, &testCheckerFactory{}, false, nonceTracker) @@ -900,7 +900,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { _, fromAddress := cltest.RandomKey{Nonce: nextNonce.Int64()}.MustInsertWithState(t, ethKeyStore) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, &testCheckerFactory{}, false, nonceTracker) @@ -938,7 +938,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { _, fromAddress := cltest.RandomKey{Nonce: nextNonce.Int64()}.MustInsertWithState(t, ethKeyStore) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, &testCheckerFactory{}, false, nonceTracker) @@ -980,7 +980,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg, &testCheckerFactory{}, false, nonceTracker) @@ -1044,7 +1044,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() lggr := logger.Test(t) txmClient := txmgr.NewEvmTxmClient(ethClient, nil) nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmClient) @@ -1597,7 +1597,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { c.EVM[0].GasEstimator.BumpPercent = ptr[uint16](0) })) localNextNonce := getLocalNextNonce(t, nonceTracker, fromAddress) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(localNextNonce, nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(localNextNonce, nil).Once() eb2 := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg2, &testCheckerFactory{}, false, nonceTracker) mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID) underpricedError := "transaction underpriced" @@ -1629,7 +1629,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { c.EVM[0].GasEstimator.TipCapDefault = gasTipCapDefault })) localNextNonce := getLocalNextNonce(t, nonceTracker, fromAddress) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(localNextNonce, nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(localNextNonce, nil).Once() eb2 := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, cfg, evmcfg2, &testCheckerFactory{}, false, nonceTracker) // Second was underpriced but above minimum @@ -1672,7 +1672,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_GasEstimationError(t *testing.T) config := evmtest.NewChainScopedConfig(t, cfg) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() lggr := logger.Test(t) txmClient := txmgr.NewEvmTxmClient(ethClient, nil) nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmClient) @@ -1741,7 +1741,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { kst := ksmocks.NewEth(t) addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, testutils.FixtureChainID).Return(addresses, nil).Once() - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() lggr := logger.Test(t) nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, nil)) eb := NewTestEthBroadcaster(t, txStore, ethClient, kst, cfg, evmcfg, &testCheckerFactory{}, false, nonceTracker) @@ -1829,7 +1829,7 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) { kst := ksmocks.NewEth(t) addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, testutils.FixtureChainID).Return(addresses, nil).Once() - ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethClient.On("NonceAt", mock.Anything, fromAddress, mock.Anything).Return(uint64(0), nil).Once() txmClient := txmgr.NewEvmTxmClient(ethClient, nil) eb := txmgr.NewEvmBroadcaster(txStore, txmClient, evmTxmCfg, txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, txBuilder, lggr, checkerFactory, false, "") err := eb.Start(ctx) diff --git a/core/chains/evm/txmgr/nonce_tracker.go b/core/chains/evm/txmgr/nonce_tracker.go index 941775b7e85..873f2595dbf 100644 --- a/core/chains/evm/txmgr/nonce_tracker.go +++ b/core/chains/evm/txmgr/nonce_tracker.go @@ -23,6 +23,7 @@ type NonceTrackerTxStore interface { type NonceTrackerClient interface { ConfiguredChainID() *big.Int PendingSequenceAt(context.Context, common.Address) (evmtypes.Nonce, error) + SequenceAt(ctx context.Context, addr common.Address, blockNum *big.Int) (evmtypes.Nonce, error) } type nonceTracker struct { @@ -69,9 +70,13 @@ func (s *nonceTracker) getSequenceForAddr(ctx context.Context, address common.Ad seq++ return seq, nil } - // Look for nonce on-chain if no tx found for address in TxStore or if error occurred - // Returns the nonce that should be used for the next transaction so no need to increment - nonce, err := s.client.PendingSequenceAt(ctx, address) + // Look for nonce on-chain if no tx found for address in TxStore or if error occurred. + // We use the mined transaction count (SequenceAt) to determine the next nonce to use instead of the pending transaction count (PendingSequenceAt). + // This allows the TXM to broadcast and track transactions for nonces starting from the last mined transaction preventing the perpetuation of a potential nonce gap. + // NOTE: Any transactions already in the mempool would be attempted to be overwritten but an older transaction can get included before being overwritten. + // Such could be the case if there was a nonce gap that gets filled unblocking the transactions. + // If that occurs, there could be short term noise in the logs surfacing that a transaction expired without ever getting a receipt. + nonce, err := s.client.SequenceAt(ctx, address, nil) if err == nil { return nonce, nil } diff --git a/core/chains/evm/txmgr/nonce_tracker_test.go b/core/chains/evm/txmgr/nonce_tracker_test.go index c9e3cbd76c3..69f7fe50dd4 100644 --- a/core/chains/evm/txmgr/nonce_tracker_test.go +++ b/core/chains/evm/txmgr/nonce_tracker_test.go @@ -61,8 +61,8 @@ func TestNonceTracker_LoadSequenceMap(t *testing.T) { randNonce1 := testutils.NewRandomPositiveInt64() randNonce2 := testutils.NewRandomPositiveInt64() - client.On("PendingNonceAt", mock.Anything, addr1).Return(uint64(randNonce1), nil).Once() - client.On("PendingNonceAt", mock.Anything, addr2).Return(uint64(randNonce2), nil).Once() + client.On("NonceAt", mock.Anything, addr1, mock.Anything).Return(uint64(randNonce1), nil).Once() //nolint:gosec // Disable G115: randNonce1 always positive + client.On("NonceAt", mock.Anything, addr2, mock.Anything).Return(uint64(randNonce2), nil).Once() //nolint:gosec // Disable G115: randNonce2 always positive nonceTracker.LoadNextSequences(ctx, enabledAddresses) seq, err := nonceTracker.GetNextSequence(ctx, addr1) @@ -205,7 +205,7 @@ func TestNonceTracker_GetNextSequence(t *testing.T) { t.Run("fails to get sequence if address is enabled, doesn't exist in map, and getSequenceForAddr fails", func(t *testing.T) { enabledAddresses := []common.Address{addr} txStore.On("FindLatestSequence", mock.Anything, addr, chainID).Return(types.Nonce(0), errors.New("no rows")).Twice() - client.On("PendingNonceAt", mock.Anything, addr).Return(uint64(0), errors.New("RPC unavailable")).Twice() + client.On("NonceAt", mock.Anything, addr, mock.Anything).Return(uint64(0), errors.New("RPC unavailable")).Twice() nonceTracker.LoadNextSequences(ctx, enabledAddresses) _, err := nonceTracker.GetNextSequence(ctx, addr) @@ -217,7 +217,7 @@ func TestNonceTracker_GetNextSequence(t *testing.T) { txStoreNonce := 4 enabledAddresses := []common.Address{addr} txStore.On("FindLatestSequence", mock.Anything, addr, chainID).Return(types.Nonce(0), errors.New("no rows")).Once() - client.On("PendingNonceAt", mock.Anything, addr).Return(uint64(0), errors.New("RPC unavailable")).Once() + client.On("NonceAt", mock.Anything, addr, mock.Anything).Return(uint64(0), errors.New("RPC unavailable")).Once() nonceTracker.LoadNextSequences(ctx, enabledAddresses) txStore.On("FindLatestSequence", mock.Anything, addr, chainID).Return(types.Nonce(txStoreNonce), nil).Once() @@ -272,8 +272,8 @@ func Test_SetNonceAfterInit(t *testing.T) { addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") enabledAddresses := []common.Address{addr} randNonce := testutils.NewRandomPositiveInt64() - client.On("PendingNonceAt", mock.Anything, addr).Return(uint64(0), errors.New("failed to retrieve nonce at startup")).Once() - client.On("PendingNonceAt", mock.Anything, addr).Return(uint64(randNonce), nil).Once() + client.On("NonceAt", mock.Anything, addr, mock.Anything).Return(uint64(0), errors.New("failed to retrieve nonce at startup")).Once() + client.On("NonceAt", mock.Anything, addr, mock.Anything).Return(uint64(randNonce), nil).Once() //nolint:gosec // Disable G115: randNonce always positive nonceTracker.LoadNextSequences(ctx, enabledAddresses) nonce, err := nonceTracker.GetNextSequence(ctx, addr) diff --git a/core/chains/evm/txmgr/txmgr_test.go b/core/chains/evm/txmgr/txmgr_test.go index c47ca85737b..b70e2fed671 100644 --- a/core/chains/evm/txmgr/txmgr_test.go +++ b/core/chains/evm/txmgr/txmgr_test.go @@ -101,7 +101,7 @@ func TestTxm_SendNativeToken_DoesNotSendToZero(t *testing.T) { keyStore := cltest.NewKeyStore(t, db).Eth() ethClient := testutils.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator()) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), keyStore) require.NoError(t, err) @@ -127,7 +127,7 @@ func TestTxm_CreateTransaction(t *testing.T) { ethClient := testutils.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator()) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst.Eth()) require.NoError(t, err) @@ -409,7 +409,7 @@ func TestTxm_CreateTransaction_OutOfEth(t *testing.T) { config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) ethClient := testutils.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator()) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), etKeyStore) require.NoError(t, err) @@ -507,7 +507,7 @@ func TestTxm_Lifecycle(t *testing.T) { keyChangeCh := make(chan struct{}) unsub := cltest.NewAwaiter() kst.On("SubscribeToKeyChanges", mock.Anything).Return(keyChangeCh, unsub.ItHappened) - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator()) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, evmConfig, evmConfig.GasEstimator(), evmConfig.Transactions(), dbConfig, dbConfig.Listener(), kst) require.NoError(t, err) @@ -562,7 +562,7 @@ func TestTxm_Reset(t *testing.T) { ethClient.On("PendingNonceAt", mock.Anything, addr).Return(uint64(128), nil).Maybe() ethClient.On("PendingNonceAt", mock.Anything, addr2).Return(uint64(44), nil).Maybe() - estimator, err := gas.NewEstimator(logger.Test(t), ethClient, cfg.EVM().ChainType(), cfg.EVM().GasEstimator()) + estimator, err := gas.NewEstimator(logger.Test(t), ethClient, cfg.EVM().ChainType(), cfg.EVM().GasEstimator(), nil) require.NoError(t, err) txm, err := makeTestEvmTxm(t, db, ethClient, estimator, cfg.EVM(), cfg.EVM().GasEstimator(), cfg.EVM().Transactions(), gcfg.Database(), gcfg.Database().Listener(), kst.Eth()) require.NoError(t, err) diff --git a/core/chains/legacyevm/chain.go b/core/chains/legacyevm/chain.go index 277d6283690..d8eed88dedf 100644 --- a/core/chains/legacyevm/chain.go +++ b/core/chains/legacyevm/chain.go @@ -10,6 +10,8 @@ import ( gotoml "github.com/pelletier/go-toml/v2" "go.uber.org/multierr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" + common "github.com/smartcontractkit/chainlink-common/pkg/chains" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" @@ -173,7 +175,7 @@ func (o ChainOpts) Validate() error { return err } -func NewTOMLChain(ctx context.Context, chain *toml.EVMConfig, opts ChainRelayOpts) (Chain, error) { +func NewTOMLChain(ctx context.Context, chain *toml.EVMConfig, opts ChainRelayOpts, clientsByChainID map[string]rollups.DAClient) (Chain, error) { err := opts.Validate() if err != nil { return nil, err @@ -185,10 +187,10 @@ func NewTOMLChain(ctx context.Context, chain *toml.EVMConfig, opts ChainRelayOpt } cfg := evmconfig.NewTOMLChainScopedConfig(chain, l) // note: per-chain validation is not necessary at this point since everything is checked earlier on boot. - return newChain(ctx, cfg, chain.Nodes, opts) + return newChain(ctx, cfg, chain.Nodes, opts, clientsByChainID) } -func newChain(ctx context.Context, cfg *evmconfig.ChainScoped, nodes []*toml.Node, opts ChainRelayOpts) (*chain, error) { +func newChain(ctx context.Context, cfg *evmconfig.ChainScoped, nodes []*toml.Node, opts ChainRelayOpts, clientsByChainID map[string]rollups.DAClient) (*chain, error) { chainID := cfg.EVM().ChainID() l := opts.Logger var client evmclient.Client @@ -243,7 +245,7 @@ func newChain(ctx context.Context, cfg *evmconfig.ChainScoped, nodes []*toml.Nod } // note: gas estimator is started as a part of the txm - txm, gasEstimator, err := newEvmTxm(opts.DS, cfg.EVM(), opts.AppConfig.EVMRPCEnabled(), opts.AppConfig.Database(), opts.AppConfig.Database().Listener(), client, l, logPoller, opts, headTracker) + txm, gasEstimator, err := newEvmTxm(opts.DS, cfg.EVM(), opts.AppConfig.EVMRPCEnabled(), opts.AppConfig.Database(), opts.AppConfig.Database().Listener(), client, l, logPoller, opts, headTracker, clientsByChainID) if err != nil { return nil, fmt.Errorf("failed to instantiate EvmTxm for chain with ID %s: %w", chainID.String(), err) } diff --git a/core/chains/legacyevm/evm_txm.go b/core/chains/legacyevm/evm_txm.go index ec7098ab56c..5af3799ec7b 100644 --- a/core/chains/legacyevm/evm_txm.go +++ b/core/chains/legacyevm/evm_txm.go @@ -7,6 +7,7 @@ import ( evmclient "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" evmconfig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" httypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/headtracker/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" @@ -24,6 +25,7 @@ func newEvmTxm( logPoller logpoller.LogPoller, opts ChainRelayOpts, headTracker httypes.HeadTracker, + clientsByChainID map[string]rollups.DAClient, ) (txm txmgr.TxManager, estimator gas.EvmFeeEstimator, err error, @@ -45,7 +47,7 @@ func newEvmTxm( // build estimator from factory if opts.GenGasEstimator == nil { - if estimator, err = gas.NewEstimator(lggr, client, cfg.ChainType(), cfg.GasEstimator()); err != nil { + if estimator, err = gas.NewEstimator(lggr, client, cfg.ChainType(), cfg.GasEstimator(), clientsByChainID); err != nil { return nil, nil, fmt.Errorf("failed to initialize estimator: %w", err) } } else { diff --git a/core/cmd/eth_keys_commands_test.go b/core/cmd/eth_keys_commands_test.go index 64835c7f28b..3442fee7e4a 100644 --- a/core/cmd/eth_keys_commands_test.go +++ b/core/cmd/eth_keys_commands_test.go @@ -93,7 +93,7 @@ func TestShell_ListETHKeys(t *testing.T) { ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(commonassets.NewLinkFromJuels(13), nil) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) c.EVM[0].NonceAutoSync = ptr(false) @@ -118,7 +118,7 @@ func TestShell_ListETHKeys_Error(t *testing.T) { ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("fake error")) ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("fake error")) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) c.EVM[0].NonceAutoSync = ptr(false) @@ -172,7 +172,7 @@ func TestShell_CreateETHKey(t *testing.T) { ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(commonassets.NewLinkFromJuels(42), nil) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) @@ -247,7 +247,7 @@ func TestShell_ImportExportETHKey_NoChains(t *testing.T) { ethClient := newEthMock(t) ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(big.NewInt(42), nil) ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(commonassets.NewLinkFromJuels(42), nil) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) c.EVM[0].NonceAutoSync = ptr(false) @@ -351,7 +351,7 @@ func TestShell_ImportExportETHKey_WithChains(t *testing.T) { t.Cleanup(func() { deleteKeyExportFile(t) }) ethClient := newEthMock(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) c.EVM[0].NonceAutoSync = ptr(false) diff --git a/core/cmd/evm_transaction_commands_test.go b/core/cmd/evm_transaction_commands_test.go index 1b593dccd84..77374418a9d 100644 --- a/core/cmd/evm_transaction_commands_test.go +++ b/core/cmd/evm_transaction_commands_test.go @@ -139,8 +139,7 @@ func TestShell_SendEther_From_Txm(t *testing.T) { ethMock := newEthMockWithTransactionsOnBlocksAssertions(t) ethMock.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) - ethMock.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Maybe() - ethMock.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethMock.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil) app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) @@ -203,8 +202,7 @@ func TestShell_SendEther_From_Txm_WEI(t *testing.T) { ethMock := newEthMockWithTransactionsOnBlocksAssertions(t) ethMock.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) - ethMock.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Maybe() - ethMock.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() + ethMock.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil) app := startNewApplicationV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].Enabled = ptr(true) diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index 3fca9d6ad09..689e7d27d26 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -474,6 +474,13 @@ func (s *Shell) runNode(c *cli.Context) error { } } + if s.Config.Capabilities().Peering().Enabled() { + err2 := app.GetKeyStore().Workflow().EnsureKey(rootCtx) + if err2 != nil { + return errors.Wrap(err2, "failed to ensure workflow key") + } + } + err2 := app.GetKeyStore().CSA().EnsureKey(rootCtx) if err2 != nil { return errors.Wrap(err2, "failed to ensure CSA key") diff --git a/core/gethwrappers/ccip/generated/registry_module_owner_custom/registry_module_owner_custom.go b/core/gethwrappers/ccip/generated/registry_module_owner_custom/registry_module_owner_custom.go index 121135075db..315d75121b1 100644 --- a/core/gethwrappers/ccip/generated/registry_module_owner_custom/registry_module_owner_custom.go +++ b/core/gethwrappers/ccip/generated/registry_module_owner_custom/registry_module_owner_custom.go @@ -31,8 +31,8 @@ var ( ) var RegistryModuleOwnerCustomMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenAdminRegistry\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AddressZero\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"CanOnlySelfRegister\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"administrator\",\"type\":\"address\"}],\"name\":\"AdministratorRegistered\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"registerAdminViaGetCCIPAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"registerAdminViaOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x60a060405234801561001057600080fd5b5060405161047e38038061047e83398101604081905261002f91610067565b6001600160a01b03811661005657604051639fabe1c160e01b815260040160405180910390fd5b6001600160a01b0316608052610097565b60006020828403121561007957600080fd5b81516001600160a01b038116811461009057600080fd5b9392505050565b6080516103cc6100b2600039600061024a01526103cc6000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063181f5a771461004657806396ea2f7a14610098578063ff12c354146100ad575b600080fd5b6100826040518060400160405280601f81526020017f52656769737472794d6f64756c654f776e6572437573746f6d20312e352e300081525081565b60405161008f91906102ef565b60405180910390f35b6100ab6100a636600461037e565b6100c0565b005b6100ab6100bb36600461037e565b61013b565b610138818273ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561010f573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061013391906103a2565b61018a565b50565b610138818273ffffffffffffffffffffffffffffffffffffffff16638fd6a6ac6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561010f573d6000803e3d6000fd5b73ffffffffffffffffffffffffffffffffffffffff811633146101fd576040517fc454d18200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff80831660048301528316602482015260440160405180910390fd5b6040517fe677ae3700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff838116600483015282811660248301527f0000000000000000000000000000000000000000000000000000000000000000169063e677ae3790604401600060405180830381600087803b15801561028e57600080fd5b505af11580156102a2573d6000803e3d6000fd5b505060405173ffffffffffffffffffffffffffffffffffffffff8085169350851691507f09590fb70af4b833346363965e043a9339e8c7d378b8a2b903c75c277faec4f990600090a35050565b60006020808352835180602085015260005b8181101561031d57858101830151858201604001528201610301565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b73ffffffffffffffffffffffffffffffffffffffff8116811461013857600080fd5b60006020828403121561039057600080fd5b813561039b8161035c565b9392505050565b6000602082840312156103b457600080fd5b815161039b8161035c56fea164736f6c6343000818000a", + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenAdminRegistry\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AddressZero\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"CanOnlySelfRegister\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"msgSender\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"RequiredRoleNotFound\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"administrator\",\"type\":\"address\"}],\"name\":\"AdministratorRegistered\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"registerAccessControlDefaultAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"registerAdminViaGetCCIPAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"registerAdminViaOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"typeAndVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60a060405234801561001057600080fd5b5060405161064a38038061064a83398101604081905261002f91610067565b6001600160a01b03811661005657604051639fabe1c160e01b815260040160405180910390fd5b6001600160a01b0316608052610097565b60006020828403121561007957600080fd5b81516001600160a01b038116811461009057600080fd5b9392505050565b6080516105986100b260003960006103db01526105986000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063181f5a771461005157806369c0081e146100a357806396ea2f7a146100b8578063ff12c354146100cb575b600080fd5b61008d6040518060400160405280601f81526020017f52656769737472794d6f64756c654f776e6572437573746f6d20312e362e300081525081565b60405161009a9190610480565b60405180910390f35b6100b66100b136600461050f565b6100de565b005b6100b66100c636600461050f565b610255565b6100b66100d936600461050f565b6102d0565b60008173ffffffffffffffffffffffffffffffffffffffff1663a217fddf6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561012b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014f9190610533565b6040517f91d148540000000000000000000000000000000000000000000000000000000081526004810182905233602482015290915073ffffffffffffffffffffffffffffffffffffffff8316906391d1485490604401602060405180830381865afa1580156101c3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101e7919061054c565b610247576040517f86e0b3440000000000000000000000000000000000000000000000000000000081523360048201526024810182905273ffffffffffffffffffffffffffffffffffffffff831660448201526064015b60405180910390fd5b610251823361031f565b5050565b6102cd818273ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102a4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102c8919061056e565b61031f565b50565b6102cd818273ffffffffffffffffffffffffffffffffffffffff16638fd6a6ac6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156102a4573d6000803e3d6000fd5b73ffffffffffffffffffffffffffffffffffffffff8116331461038e576040517fc454d18200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff80831660048301528316602482015260440161023e565b6040517fe677ae3700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff838116600483015282811660248301527f0000000000000000000000000000000000000000000000000000000000000000169063e677ae3790604401600060405180830381600087803b15801561041f57600080fd5b505af1158015610433573d6000803e3d6000fd5b505060405173ffffffffffffffffffffffffffffffffffffffff8085169350851691507f09590fb70af4b833346363965e043a9339e8c7d378b8a2b903c75c277faec4f990600090a35050565b60006020808352835180602085015260005b818110156104ae57858101830151858201604001528201610492565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b73ffffffffffffffffffffffffffffffffffffffff811681146102cd57600080fd5b60006020828403121561052157600080fd5b813561052c816104ed565b9392505050565b60006020828403121561054557600080fd5b5051919050565b60006020828403121561055e57600080fd5b8151801515811461052c57600080fd5b60006020828403121561058057600080fd5b815161052c816104ed56fea164736f6c6343000818000a", } var RegistryModuleOwnerCustomABI = RegistryModuleOwnerCustomMetaData.ABI @@ -193,6 +193,18 @@ func (_RegistryModuleOwnerCustom *RegistryModuleOwnerCustomCallerSession) TypeAn return _RegistryModuleOwnerCustom.Contract.TypeAndVersion(&_RegistryModuleOwnerCustom.CallOpts) } +func (_RegistryModuleOwnerCustom *RegistryModuleOwnerCustomTransactor) RegisterAccessControlDefaultAdmin(opts *bind.TransactOpts, token common.Address) (*types.Transaction, error) { + return _RegistryModuleOwnerCustom.contract.Transact(opts, "registerAccessControlDefaultAdmin", token) +} + +func (_RegistryModuleOwnerCustom *RegistryModuleOwnerCustomSession) RegisterAccessControlDefaultAdmin(token common.Address) (*types.Transaction, error) { + return _RegistryModuleOwnerCustom.Contract.RegisterAccessControlDefaultAdmin(&_RegistryModuleOwnerCustom.TransactOpts, token) +} + +func (_RegistryModuleOwnerCustom *RegistryModuleOwnerCustomTransactorSession) RegisterAccessControlDefaultAdmin(token common.Address) (*types.Transaction, error) { + return _RegistryModuleOwnerCustom.Contract.RegisterAccessControlDefaultAdmin(&_RegistryModuleOwnerCustom.TransactOpts, token) +} + func (_RegistryModuleOwnerCustom *RegistryModuleOwnerCustomTransactor) RegisterAdminViaGetCCIPAdmin(opts *bind.TransactOpts, token common.Address) (*types.Transaction, error) { return _RegistryModuleOwnerCustom.contract.Transact(opts, "registerAdminViaGetCCIPAdmin", token) } @@ -374,6 +386,8 @@ func (_RegistryModuleOwnerCustom *RegistryModuleOwnerCustom) Address() common.Ad type RegistryModuleOwnerCustomInterface interface { TypeAndVersion(opts *bind.CallOpts) (string, error) + RegisterAccessControlDefaultAdmin(opts *bind.TransactOpts, token common.Address) (*types.Transaction, error) + RegisterAdminViaGetCCIPAdmin(opts *bind.TransactOpts, token common.Address) (*types.Transaction, error) RegisterAdminViaOwner(opts *bind.TransactOpts, token common.Address) (*types.Transaction, 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 850bfe7c410..38ea40b86a0 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 @@ -19,7 +19,7 @@ nonce_manager: ../../../contracts/solc/v0.8.24/NonceManager/NonceManager.abi ../ offramp: ../../../contracts/solc/v0.8.24/OffRamp/OffRamp.abi ../../../contracts/solc/v0.8.24/OffRamp/OffRamp.bin d20e6c0baf08926b341c31ed0018983e135a75b7d120591de49ca4ece3824d0b onramp: ../../../contracts/solc/v0.8.24/OnRamp/OnRamp.abi ../../../contracts/solc/v0.8.24/OnRamp/OnRamp.bin 2bf74188a997218502031f177cb2df505b272d66b25fd341a741289e77380c59 ping_pong_demo: ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.abi ../../../contracts/solc/v0.8.24/PingPongDemo/PingPongDemo.bin 24b4415a883a470d65c484be0fa20714a46b1c9262db205f1c958017820307b2 -registry_module_owner_custom: ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.abi ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.bin 75be86323c227917a9bbc3f799d7ed02f92db546653a36db30ed0ebe64461353 +registry_module_owner_custom: ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.abi ../../../contracts/solc/v0.8.24/RegistryModuleOwnerCustom/RegistryModuleOwnerCustom.bin 0fc277a0b512db4e20b5a32a775b94ed2c0d342d8237511de78c94f7dacad428 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 diff --git a/core/scripts/chaincli/handler/report.go b/core/scripts/chaincli/handler/report.go index eb4ce5c83ac..2bd09c6543d 100644 --- a/core/scripts/chaincli/handler/report.go +++ b/core/scripts/chaincli/handler/report.go @@ -210,7 +210,7 @@ func NewOCR2Transaction(raw map[string]interface{}) (*OCR2Transaction, error) { encoder: evm.EVMAutomationEncoder20{}, abi: contract, raw: raw, - tx: tx, + tx: &tx, }, nil } @@ -218,7 +218,7 @@ type OCR2Transaction struct { encoder evm.EVMAutomationEncoder20 abi abi.ABI raw map[string]interface{} - tx types.Transaction + tx *types.Transaction } func (t *OCR2Transaction) TransactionHash() common.Hash { @@ -252,7 +252,7 @@ func (t *OCR2Transaction) To() *common.Address { func (t *OCR2Transaction) From() (common.Address, error) { switch t.tx.Type() { case 2: - from, err := types.Sender(types.NewLondonSigner(t.tx.ChainId()), &t.tx) + from, err := types.Sender(types.NewLondonSigner(t.tx.ChainId()), t.tx) if err != nil { return common.Address{}, fmt.Errorf("failed to get from addr: %s", err) } else { diff --git a/core/scripts/go.mod b/core/scripts/go.mod index d5100c6f3d2..0392bfab5c2 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -288,7 +288,7 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 // indirect github.com/smartcontractkit/chain-selectors v1.0.27 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 005d95779ae..3e5bc90f90f 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1090,8 +1090,8 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 h1:+VR9yKhbz+iCtWnCabaCDP18lIqGnk7YvGQIbJYgDrs= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 h1:GWjim4uGGFbye4XbJP0cPAbARhc8u3cAJU8jLYy0mXM= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7 h1:AGi0kAtMRW1zl1h7sGw+3CKO4Nlev6iA08YfEcgJCGs= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index d37e812a19d..f5a3aaa7b59 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1405,6 +1405,7 @@ func TestConfig_full(t *testing.T) { if got.EVM[c].GasEstimator.DAOracle.OracleAddress == nil { got.EVM[c].GasEstimator.DAOracle.OracleAddress = new(types.EIP55Address) } + if got.EVM[c].GasEstimator.DAOracle.CustomGasPriceCalldata == nil { got.EVM[c].GasEstimator.DAOracle.CustomGasPriceCalldata = new(string) } diff --git a/core/services/headreporter/prometheus_reporter_test.go b/core/services/headreporter/prometheus_reporter_test.go index d6fc0f8e938..ab85bac1ac0 100644 --- a/core/services/headreporter/prometheus_reporter_test.go +++ b/core/services/headreporter/prometheus_reporter_test.go @@ -109,7 +109,7 @@ func newLegacyChainContainer(t *testing.T, db *sqlx.DB) legacyevm.LegacyChainCon config, dbConfig, evmConfig := txmgr.MakeTestConfigs(t) keyStore := cltest.NewKeyStore(t, db).Eth() ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config.ChainType(), evmConfig.GasEstimator()) + estimator, err := gas.NewEstimator(logger.TestLogger(t), ethClient, config.ChainType(), evmConfig.GasEstimator(), nil) require.NoError(t, err) lggr := logger.TestLogger(t) lpOpts := logpoller.Opts{ diff --git a/core/services/keystore/keys/workflowkey/export.go b/core/services/keystore/keys/workflowkey/export.go new file mode 100644 index 00000000000..bb0430a6701 --- /dev/null +++ b/core/services/keystore/keys/workflowkey/export.go @@ -0,0 +1,44 @@ +package workflowkey + +import ( + "github.com/ethereum/go-ethereum/accounts/keystore" + + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys" + "github.com/smartcontractkit/chainlink/v2/core/utils" +) + +const keyTypeIdentifier = "Workflow" + +func FromEncryptedJSON(keyJSON []byte, password string) (Key, error) { + return keys.FromEncryptedJSON( + keyTypeIdentifier, + keyJSON, + password, + adulteratedPassword, + func(_ keys.EncryptedKeyExport, rawPrivKey []byte) (Key, error) { + return Raw(rawPrivKey).Key(), nil + }, + ) +} + +func (k Key) ToEncryptedJSON(password string, scryptParams utils.ScryptParams) (export []byte, err error) { + return keys.ToEncryptedJSON( + keyTypeIdentifier, + k.Raw(), + k, + password, + scryptParams, + adulteratedPassword, + func(id string, key Key, cryptoJSON keystore.CryptoJSON) keys.EncryptedKeyExport { + return keys.EncryptedKeyExport{ + KeyType: id, + PublicKey: key.PublicKeyString(), + Crypto: cryptoJSON, + } + }, + ) +} + +func adulteratedPassword(password string) string { + return "workflowkey" + password +} diff --git a/core/services/keystore/keys/workflowkey/key.go b/core/services/keystore/keys/workflowkey/key.go new file mode 100644 index 00000000000..ce8560303e0 --- /dev/null +++ b/core/services/keystore/keys/workflowkey/key.go @@ -0,0 +1,107 @@ +package workflowkey + +import ( + cryptorand "crypto/rand" + "encoding/hex" + "errors" + "fmt" + + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/nacl/box" +) + +type Raw []byte + +func (raw Raw) Key() Key { + privateKey := [32]byte(raw) + return Key{ + privateKey: &privateKey, + publicKey: curve25519PubKeyFromPrivateKey(privateKey), + } +} + +func (raw Raw) String() string { + return fmt.Sprintf("<%s Raw Private Key>", keyTypeIdentifier) +} + +func (raw Raw) GoString() string { + return raw.String() +} + +func (raw Raw) Bytes() []byte { + return ([]byte)(raw) +} + +type Key struct { + privateKey *[curve25519.PointSize]byte + publicKey *[curve25519.PointSize]byte +} + +func New() (Key, error) { + publicKey, privateKey, err := box.GenerateKey(cryptorand.Reader) + if err != nil { + return Key{}, err + } + + return Key{ + privateKey: privateKey, + publicKey: publicKey, + }, nil +} + +func (k Key) PublicKey() [curve25519.PointSize]byte { + return *k.publicKey +} + +func (k Key) PublicKeyString() string { + return hex.EncodeToString(k.publicKey[:]) +} + +func (k Key) ID() string { + return k.PublicKeyString() +} + +func (k Key) Raw() Raw { + raw := make([]byte, curve25519.PointSize) + copy(raw, k.privateKey[:]) + return Raw(raw) +} + +func (k Key) String() string { + return fmt.Sprintf("%sKey{PrivateKey: , PublicKey: %s}", keyTypeIdentifier, *k.publicKey) +} + +func (k Key) GoString() string { + return k.String() +} + +// Encrypt encrypts a message using the public key +func (k Key) Encrypt(plaintext []byte) ([]byte, error) { + publicKey := k.PublicKey() + encrypted, err := box.SealAnonymous(nil, plaintext, &publicKey, cryptorand.Reader) + if err != nil { + return nil, err + } + + return encrypted, nil +} + +// Decrypt decrypts a message that was encrypted using the private key +func (k Key) Decrypt(ciphertext []byte) (plaintext []byte, err error) { + publicKey := k.PublicKey() + decrypted, success := box.OpenAnonymous(nil, ciphertext, &publicKey, k.privateKey) + if !success { + return nil, errors.New("decryption failed") + } + + return decrypted, nil +} + +func curve25519PubKeyFromPrivateKey(privateKey [curve25519.PointSize]byte) *[curve25519.PointSize]byte { + var publicKey [curve25519.PointSize]byte + + // Derive the public key + curve25519.ScalarBaseMult(&publicKey, &privateKey) + + return &publicKey +} diff --git a/core/services/keystore/keys/workflowkey/key_test.go b/core/services/keystore/keys/workflowkey/key_test.go new file mode 100644 index 00000000000..3e3a9413a90 --- /dev/null +++ b/core/services/keystore/keys/workflowkey/key_test.go @@ -0,0 +1,88 @@ +package workflowkey + +import ( + cryptorand "crypto/rand" + "encoding/hex" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/nacl/box" +) + +func TestNew(t *testing.T) { + key, err := New() + require.NoError(t, err) + + assert.NotNil(t, key.PublicKey) + assert.NotNil(t, key.privateKey) +} + +func TestPublicKey(t *testing.T) { + key, err := New() + require.NoError(t, err) + + assert.Equal(t, *key.publicKey, key.PublicKey()) +} + +func TestEncryptKeyRawPrivateKey(t *testing.T) { + privKey, err := New() + require.NoError(t, err) + + privateKey := privKey.Raw() + + assert.Equal(t, "", privateKey.String()) + assert.Equal(t, privateKey.String(), privateKey.GoString()) +} + +func TestEncryptKeyFromRawPrivateKey(t *testing.T) { + boxPubKey, boxPrivKey, err := box.GenerateKey(cryptorand.Reader) + require.NoError(t, err) + + privKey := make([]byte, 32) + copy(privKey, boxPrivKey[:]) + key := Raw(privKey).Key() + + assert.Equal(t, boxPubKey, key.publicKey) + assert.Equal(t, boxPrivKey, key.privateKey) + assert.Equal(t, key.String(), key.GoString()) + + byteBoxPubKey := make([]byte, 32) + copy(byteBoxPubKey, boxPubKey[:]) + + assert.Equal(t, hex.EncodeToString(byteBoxPubKey), key.PublicKeyString()) + assert.Equal(t, fmt.Sprintf("WorkflowKey{PrivateKey: , PublicKey: %s}", byteBoxPubKey), key.String()) +} + +func TestPublicKeyStringAndID(t *testing.T) { + key := "my-test-public-key" + var pubkey [32]byte + copy(pubkey[:], key) + k := Key{ + publicKey: &pubkey, + } + + expected := hex.EncodeToString([]byte(key)) + // given the key is a [32]byte we need to ensure the encoded string is 64 character long + for len(expected) < 64 { + expected += "0" + } + + assert.Equal(t, expected, k.PublicKeyString()) + assert.Equal(t, expected, k.ID()) +} + +func TestDecrypt(t *testing.T) { + key, err := New() + require.NoError(t, err) + + secret := []byte("my-secret") + ciphertext, err := key.Encrypt(secret) + require.NoError(t, err) + + plaintext, err := key.Decrypt(ciphertext) + require.NoError(t, err) + + assert.Equal(t, secret, plaintext) +} diff --git a/core/services/keystore/master.go b/core/services/keystore/master.go index 47136f1f2ec..72677a166a3 100644 --- a/core/services/keystore/master.go +++ b/core/services/keystore/master.go @@ -21,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/solkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/starkkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -45,6 +46,7 @@ type Master interface { StarkNet() StarkNet Aptos() Aptos VRF() VRF + Workflow() Workflow Unlock(ctx context.Context, password string) error IsEmpty(ctx context.Context) (bool, error) } @@ -61,6 +63,7 @@ type master struct { starknet *starknet aptos *aptos vrf *vrf + workflow *workflow } func New(ds sqlutil.DataSource, scryptParams utils.ScryptParams, lggr logger.Logger) Master { @@ -89,6 +92,7 @@ func newMaster(ds sqlutil.DataSource, scryptParams utils.ScryptParams, lggr logg starknet: newStarkNetKeyStore(km), aptos: newAptosKeyStore(km), vrf: newVRFKeyStore(km), + workflow: newWorkflowKeyStore(km), } } @@ -132,6 +136,10 @@ func (ks *master) VRF() VRF { return ks.vrf } +func (ks *master) Workflow() Workflow { + return ks.workflow +} + type ORM interface { isEmpty(context.Context) (bool, error) saveEncryptedKeyRing(context.Context, *encryptedKeyRing, ...func(sqlutil.DataSource) error) error @@ -267,6 +275,8 @@ func GetFieldNameForKey(unknownKey Key) (string, error) { return "Aptos", nil case vrfkey.KeyV2: return "VRF", nil + case workflowkey.Key: + return "Workflow", nil } return "", fmt.Errorf("unknown key type: %T", unknownKey) } diff --git a/core/services/keystore/mocks/master.go b/core/services/keystore/mocks/master.go index 687449db98d..7c86001bc54 100644 --- a/core/services/keystore/mocks/master.go +++ b/core/services/keystore/mocks/master.go @@ -595,6 +595,53 @@ func (_c *Master_VRF_Call) RunAndReturn(run func() keystore.VRF) *Master_VRF_Cal return _c } +// Workflow provides a mock function with given fields: +func (_m *Master) Workflow() keystore.Workflow { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Workflow") + } + + var r0 keystore.Workflow + if rf, ok := ret.Get(0).(func() keystore.Workflow); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(keystore.Workflow) + } + } + + return r0 +} + +// Master_Workflow_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Workflow' +type Master_Workflow_Call struct { + *mock.Call +} + +// Workflow is a helper method to define mock.On call +func (_e *Master_Expecter) Workflow() *Master_Workflow_Call { + return &Master_Workflow_Call{Call: _e.mock.On("Workflow")} +} + +func (_c *Master_Workflow_Call) Run(run func()) *Master_Workflow_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Master_Workflow_Call) Return(_a0 keystore.Workflow) *Master_Workflow_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Master_Workflow_Call) RunAndReturn(run func() keystore.Workflow) *Master_Workflow_Call { + _c.Call.Return(run) + return _c +} + // NewMaster creates a new instance of Master. 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 NewMaster(t interface { diff --git a/core/services/keystore/models.go b/core/services/keystore/models.go index d5eec6802b9..e0b53ef95e4 100644 --- a/core/services/keystore/models.go +++ b/core/services/keystore/models.go @@ -21,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/solkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/starkkey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/vrfkey" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" "github.com/smartcontractkit/chainlink/v2/core/utils" ) @@ -158,6 +159,7 @@ type keyRing struct { StarkNet map[string]starkkey.Key Aptos map[string]aptoskey.Key VRF map[string]vrfkey.KeyV2 + Workflow map[string]workflowkey.Key LegacyKeys LegacyKeyStorage } @@ -173,6 +175,7 @@ func newKeyRing() *keyRing { StarkNet: make(map[string]starkkey.Key), Aptos: make(map[string]aptoskey.Key), VRF: make(map[string]vrfkey.KeyV2), + Workflow: make(map[string]workflowkey.Key), } } diff --git a/core/services/keystore/workflow.go b/core/services/keystore/workflow.go new file mode 100644 index 00000000000..2f3ac158681 --- /dev/null +++ b/core/services/keystore/workflow.go @@ -0,0 +1,163 @@ +package keystore + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" +) + +// ErrWorkflowKeyExists describes the error when the workflow key already exists +var ErrWorkflowKeyExists = errors.New("can only have 1 Workflow key") + +type Workflow interface { + Get(id string) (workflowkey.Key, error) + GetAll() ([]workflowkey.Key, error) + Create(ctx context.Context) (workflowkey.Key, error) + Add(ctx context.Context, key workflowkey.Key) error + Delete(ctx context.Context, id string) (workflowkey.Key, error) + Import(ctx context.Context, keyJSON []byte, password string) (workflowkey.Key, error) + Export(id string, password string) ([]byte, error) + EnsureKey(ctx context.Context) error +} + +type workflow struct { + *keyManager +} + +var _ Workflow = &workflow{} + +func newWorkflowKeyStore(km *keyManager) *workflow { + return &workflow{ + km, + } +} + +func (ks *workflow) Get(id string) (workflowkey.Key, error) { + ks.lock.RLock() + defer ks.lock.RUnlock() + if ks.isLocked() { + return workflowkey.Key{}, ErrLocked + } + return ks.getByID(id) +} + +func (ks *workflow) GetAll() (keys []workflowkey.Key, _ error) { + ks.lock.RLock() + defer ks.lock.RUnlock() + if ks.isLocked() { + return nil, ErrLocked + } + for _, key := range ks.keyRing.Workflow { + keys = append(keys, key) + } + return keys, nil +} + +func (ks *workflow) Create(ctx context.Context) (workflowkey.Key, error) { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return workflowkey.Key{}, ErrLocked + } + // Ensure you can only have one Workflow at a time. + if len(ks.keyRing.Workflow) > 0 { + return workflowkey.Key{}, ErrWorkflowKeyExists + } + + key, err := workflowkey.New() + if err != nil { + return workflowkey.Key{}, err + } + return key, ks.safeAddKey(ctx, key) +} + +func (ks *workflow) Add(ctx context.Context, key workflowkey.Key) error { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return ErrLocked + } + if len(ks.keyRing.Workflow) > 0 { + return ErrWorkflowKeyExists + } + return ks.safeAddKey(ctx, key) +} + +func (ks *workflow) Delete(ctx context.Context, id string) (workflowkey.Key, error) { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return workflowkey.Key{}, ErrLocked + } + key, err := ks.getByID(id) + if err != nil { + return workflowkey.Key{}, err + } + + err = ks.safeRemoveKey(ctx, key) + + return key, err +} + +func (ks *workflow) Import(ctx context.Context, keyJSON []byte, password string) (workflowkey.Key, error) { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return workflowkey.Key{}, ErrLocked + } + + key, err := workflowkey.FromEncryptedJSON(keyJSON, password) + if err != nil { + return workflowkey.Key{}, errors.Wrap(err, "WorkflowKeyStore#ImportKey failed to decrypt key") + } + if _, found := ks.keyRing.Workflow[key.ID()]; found { + return workflowkey.Key{}, fmt.Errorf("key with ID %s already exists", key.ID()) + } + return key, ks.keyManager.safeAddKey(ctx, key) +} + +func (ks *workflow) Export(id string, password string) ([]byte, error) { + ks.lock.RLock() + defer ks.lock.RUnlock() + if ks.isLocked() { + return nil, ErrLocked + } + key, err := ks.getByID(id) + if err != nil { + return nil, err + } + return key.ToEncryptedJSON(password, ks.scryptParams) +} + +// EnsureKey verifies whether the Workflow key has been seeded, if not, it creates it. +func (ks *workflow) EnsureKey(ctx context.Context) error { + ks.lock.Lock() + defer ks.lock.Unlock() + if ks.isLocked() { + return ErrLocked + } + + if len(ks.keyRing.Workflow) > 0 { + return nil + } + + key, err := workflowkey.New() + if err != nil { + return err + } + + ks.logger.Infof("Created Workflow key with ID %s", key.ID()) + + return ks.safeAddKey(ctx, key) +} + +func (ks *workflow) getByID(id string) (workflowkey.Key, error) { + key, found := ks.keyRing.Workflow[id] + if !found { + return workflowkey.Key{}, KeyNotFoundError{ID: id, KeyType: "Encryption"} + } + return key, nil +} diff --git a/core/services/keystore/workflow_test.go b/core/services/keystore/workflow_test.go new file mode 100644 index 00000000000..051ebdb76a7 --- /dev/null +++ b/core/services/keystore/workflow_test.go @@ -0,0 +1,178 @@ +package keystore_test + +import ( + "context" + "fmt" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/workflowkey" +) + +func Test_EncryptionKeyStore_E2E(t *testing.T) { + db := pgtest.NewSqlxDB(t) + keyStore := keystore.ExposedNewMaster(t, db) + require.NoError(t, keyStore.Unlock(testutils.Context(t), cltest.Password)) + ks := keyStore.Workflow() + reset := func() { + ctx := context.Background() // Executed on cleanup + _, err := db.Exec("DELETE FROM encrypted_key_rings") + require.NoError(t, err) + keyStore.ResetXXXTestOnly() + require.NoError(t, keyStore.Unlock(ctx, cltest.Password)) + } + + t.Run("initializes with an empty state", func(t *testing.T) { + defer reset() + keys, err := ks.GetAll() + require.NoError(t, err) + require.Empty(t, keys) + }) + + t.Run("errors when getting non-existent ID", func(t *testing.T) { + defer reset() + _, err := ks.Get("non-existent-id") + require.Error(t, err) + }) + + t.Run("creates a key", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + key, err := ks.Create(ctx) + require.NoError(t, err) + retrievedKey, err := ks.Get(key.ID()) + require.NoError(t, err) + require.Equal(t, key, retrievedKey) + + t.Run("prevents creating more than one key", func(t *testing.T) { + ctx := testutils.Context(t) + k, err2 := ks.Create(ctx) + + assert.Zero(t, k) + require.Error(t, err2) + assert.True(t, errors.Is(err2, keystore.ErrWorkflowKeyExists)) + }) + }) + + t.Run("imports and exports a key", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + key, err := ks.Create(ctx) + require.NoError(t, err) + exportJSON, err := ks.Export(key.ID(), cltest.Password) + require.NoError(t, err) + _, err = ks.Delete(ctx, key.ID()) + require.NoError(t, err) + _, err = ks.Get(key.ID()) + require.Error(t, err) + importedKey, err := ks.Import(ctx, exportJSON, cltest.Password) + require.NoError(t, err) + require.Equal(t, key.ID(), importedKey.ID()) + retrievedKey, err := ks.Get(key.ID()) + require.NoError(t, err) + require.Equal(t, importedKey, retrievedKey) + + t.Run("prevents importing more than one key", func(t *testing.T) { + k, err2 := ks.Import(testutils.Context(t), exportJSON, cltest.Password) + + assert.Zero(t, k) + require.Error(t, err2) + assert.Equal(t, fmt.Sprintf("key with ID %s already exists", key.ID()), err2.Error()) + }) + + t.Run("fails to import malformed key", func(t *testing.T) { + k, err2 := ks.Import(testutils.Context(t), []byte(""), cltest.Password) + + assert.Zero(t, k) + require.Error(t, err2) + }) + + t.Run("fails to export non-existent key", func(t *testing.T) { + exportJSON, err = ks.Export("non-existent", cltest.Password) + + require.Error(t, err) + assert.Empty(t, exportJSON) + }) + }) + + t.Run("adds an externally created key / deletes a key", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + newKey, err := workflowkey.New() + require.NoError(t, err) + err = ks.Add(ctx, newKey) + require.NoError(t, err) + keys, err := ks.GetAll() + require.NoError(t, err) + require.Len(t, keys, 1) + _, err = ks.Delete(ctx, newKey.ID()) + require.NoError(t, err) + keys, err = ks.GetAll() + require.NoError(t, err) + require.Empty(t, keys) + _, err = ks.Get(newKey.ID()) + require.Error(t, err) + + t.Run("prevents adding more than one key", func(t *testing.T) { + ctx := testutils.Context(t) + err = ks.Add(ctx, newKey) + require.NoError(t, err) + + err = ks.Add(ctx, newKey) + + require.Error(t, err) + assert.True(t, errors.Is(err, keystore.ErrWorkflowKeyExists)) + }) + + t.Run("fails to delete non-existent key", func(t *testing.T) { + k, err2 := ks.Delete(testutils.Context(t), "non-existent") + + assert.Zero(t, k) + require.Error(t, err2) + }) + }) + + t.Run("adds an externally created key/ensures it already exists", func(t *testing.T) { + defer reset() + ctx := testutils.Context(t) + + newKey, err := workflowkey.New() + require.NoError(t, err) + err = ks.Add(ctx, newKey) + require.NoError(t, err) + + err = keyStore.Workflow().EnsureKey(ctx) + require.NoError(t, err) + keys, err2 := ks.GetAll() + require.NoError(t, err2) + + require.Len(t, keys, 1) + require.Equal(t, newKey.ID(), keys[0].ID()) + require.Equal(t, newKey.PublicKey(), keys[0].PublicKey()) + }) + + t.Run("auto creates a key if it doesn't exists when trying to ensure it already exists", 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) + }) +} diff --git a/core/services/llo/delegate.go b/core/services/llo/delegate.go index 70df901c730..7c05ffbe52a 100644 --- a/core/services/llo/delegate.go +++ b/core/services/llo/delegate.go @@ -56,6 +56,7 @@ type DelegateConfig struct { RetirementReportCache RetirementReportCache RetirementReportCodec datastreamsllo.RetirementReportCodec ShouldRetireCache datastreamsllo.ShouldRetireCache + EAMonitoringEndpoint ocrcommontypes.MonitoringEndpoint // OCR3 TraceLogging bool @@ -65,7 +66,7 @@ type DelegateConfig struct { ContractConfigTrackers []ocr2types.ContractConfigTracker ContractTransmitter ocr3types.ContractTransmitter[llotypes.ReportInfo] Database ocr3types.Database - MonitoringEndpoint ocrcommontypes.MonitoringEndpoint + OCR3MonitoringEndpoint ocrcommontypes.MonitoringEndpoint OffchainConfigDigester ocr2types.OffchainConfigDigester OffchainKeyring ocr2types.OffchainKeyring OnchainKeyring ocr3types.OnchainKeyring[llotypes.ReportInfo] @@ -93,7 +94,7 @@ func NewDelegate(cfg DelegateConfig) (job.ServiceCtx, error) { var t TelemeterService if cfg.CaptureEATelemetry { - t = NewTelemeterService(lggr, cfg.MonitoringEndpoint) + t = NewTelemeterService(lggr, cfg.EAMonitoringEndpoint) } else { t = NullTelemeter } @@ -131,7 +132,7 @@ func (d *delegate) Start(ctx context.Context) error { Database: d.cfg.Database, LocalConfig: d.cfg.LocalConfig, Logger: ocrLogger, - MonitoringEndpoint: d.cfg.MonitoringEndpoint, + MonitoringEndpoint: d.cfg.OCR3MonitoringEndpoint, OffchainConfigDigester: d.cfg.OffchainConfigDigester, OffchainKeyring: d.cfg.OffchainKeyring, OnchainKeyring: d.cfg.OnchainKeyring, diff --git a/core/services/llo/onchain_channel_definition_cache.go b/core/services/llo/onchain_channel_definition_cache.go index 31b3d87bb41..8467a84aaef 100644 --- a/core/services/llo/onchain_channel_definition_cache.go +++ b/core/services/llo/onchain_channel_definition_cache.go @@ -280,35 +280,33 @@ func (c *channelDefinitionCache) scanFromBlockNum() int64 { func (c *channelDefinitionCache) fetchLatestLoop() { defer c.wg.Done() - var fetchCh chan struct{} + var cancel context.CancelFunc = func() {} for { select { case latest := <-c.newLogCh: // kill the old retry loop if any - if fetchCh != nil { - close(fetchCh) - } + cancel() - fetchCh = make(chan struct{}) + var ctx context.Context + ctx, cancel = context.WithCancel(context.Background()) c.wg.Add(1) - go c.fetchLoop(fetchCh, latest) + go c.fetchLoop(ctx, latest) case <-c.chStop: + // kill the old retry loop if any + cancel() return } } } -func (c *channelDefinitionCache) fetchLoop(closeCh chan struct{}, log *channel_config_store.ChannelConfigStoreNewChannelDefinition) { +func (c *channelDefinitionCache) fetchLoop(ctx context.Context, log *channel_config_store.ChannelConfigStoreNewChannelDefinition) { defer c.wg.Done() b := utils.NewHTTPFetchBackoff() var attemptCnt int - ctx, cancel := services.StopChan(c.chStop).NewCtx() - defer cancel() - err := c.fetchAndSetChannelDefinitions(ctx, log) if err == nil { c.lggr.Debugw("Set new channel definitions", "donID", c.donID, "version", log.Version, "url", log.Url, "sha", fmt.Sprintf("%x", log.Sha)) @@ -318,7 +316,7 @@ func (c *channelDefinitionCache) fetchLoop(closeCh chan struct{}, log *channel_c for { select { - case <-closeCh: + case <-ctx.Done(): return case <-time.After(b.Duration()): attemptCnt++ diff --git a/core/services/ocr2/delegate.go b/core/services/ocr2/delegate.go index ce2ad8ae763..371eccdbe89 100644 --- a/core/services/ocr2/delegate.go +++ b/core/services/ocr2/delegate.go @@ -1034,6 +1034,8 @@ func (d *Delegate) newServicesLLO( lggr.Infof("Using on-chain signing keys for LLO job %d (%s): %v", jb.ID, jb.Name.ValueOrZero(), kbm) kr := llo.NewOnchainKeyring(lggr, kbm) + telemetryContractID := fmt.Sprintf("%s/%d", spec.ContractID, pluginCfg.DonID) + cfg := llo.DelegateConfig{ Logger: lggr, DataSource: d.ds, @@ -1047,6 +1049,7 @@ func (d *Delegate) newServicesLLO( RetirementReportCache: d.retirementReportCache, ShouldRetireCache: provider.ShouldRetireCache(), RetirementReportCodec: datastreamsllo.StandardRetirementReportCodec{}, + EAMonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, telemetryContractID, synchronization.EnhancedEAMercury), TraceLogging: d.cfg.OCR2().TraceLogging(), BinaryNetworkEndpointFactory: d.peerWrapper.Peer2, @@ -1055,7 +1058,7 @@ func (d *Delegate) newServicesLLO( ContractConfigTrackers: provider.ContractConfigTrackers(), Database: ocrDB, LocalConfig: lc, - MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, fmt.Sprintf("%d", pluginCfg.DonID), synchronization.EnhancedEAMercury), + OCR3MonitoringEndpoint: d.monitoringEndpointGen.GenMonitoringEndpoint(rid.Network, rid.ChainID, telemetryContractID, synchronization.OCR3Mercury), OffchainConfigDigester: provider.OffchainConfigDigester(), OffchainKeyring: kb, OnchainKeyring: kr, diff --git a/core/services/ocr2/plugins/promwrapper/plugin.go b/core/services/ocr2/plugins/promwrapper/plugin.go index cc6c9d135dd..aa60ab88005 100644 --- a/core/services/ocr2/plugins/promwrapper/plugin.go +++ b/core/services/ocr2/plugins/promwrapper/plugin.go @@ -7,15 +7,23 @@ import ( "context" "fmt" "math/big" - "sync" "time" "github.com/ethereum/go-ethereum/common" + "github.com/patrickmn/go-cache" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) +const ( + // defaultExpiration is the default expiration time for cache items. + defaultExpiration = 30 * time.Minute + + // defaultCleanupInterval is the default interval for cache cleanup. + defaultCleanupInterval = 5 * time.Minute +) + // Type assertions, buckets and labels. var ( _ types.ReportingPlugin = &promPlugin{} @@ -160,10 +168,10 @@ type ( chainID *big.Int oracleID string configDigest string - queryEndTimes sync.Map - observationEndTimes sync.Map - reportEndTimes sync.Map - acceptFinalizedReportEndTimes sync.Map + queryEndTimes *cache.Cache + observationEndTimes *cache.Cache + reportEndTimes *cache.Cache + acceptFinalizedReportEndTimes *cache.Cache prometheusBackend PrometheusBackend } ) @@ -223,13 +231,17 @@ func New( } return &promPlugin{ - wrapped: plugin, - name: name, - chainType: chainType, - chainID: chainID, - oracleID: fmt.Sprintf("%d", config.OracleID), - configDigest: common.Bytes2Hex(config.ConfigDigest[:]), - prometheusBackend: prometheusBackend, + wrapped: plugin, + name: name, + chainType: chainType, + chainID: chainID, + oracleID: fmt.Sprintf("%d", config.OracleID), + configDigest: common.Bytes2Hex(config.ConfigDigest[:]), + prometheusBackend: prometheusBackend, + queryEndTimes: cache.New(defaultExpiration, defaultCleanupInterval), + observationEndTimes: cache.New(defaultExpiration, defaultCleanupInterval), + reportEndTimes: cache.New(defaultExpiration, defaultCleanupInterval), + acceptFinalizedReportEndTimes: cache.New(defaultExpiration, defaultCleanupInterval), } } @@ -238,7 +250,7 @@ func (p *promPlugin) Query(ctx context.Context, timestamp types.ReportTimestamp) defer func() { duration := float64(time.Now().UTC().Sub(start)) p.prometheusBackend.SetQueryDuration(getLabelsValues(p, timestamp), duration) - p.queryEndTimes.Store(timestamp, time.Now().UTC()) // note time at end of Query() + p.setEndTime(timestamp, p.queryEndTimes) // note time at end of Query() }() return p.wrapped.Query(ctx, timestamp) @@ -249,17 +261,16 @@ func (p *promPlugin) Observation(ctx context.Context, timestamp types.ReportTime // Report latency between Query() and Observation(). labelValues := getLabelsValues(p, timestamp) - if queryEndTime, ok := p.queryEndTimes.Load(timestamp); ok { + if queryEndTime, ok := p.queryEndTimes.Get(timestampToKey(timestamp)); ok { latency := float64(start.Sub(queryEndTime.(time.Time))) p.prometheusBackend.SetQueryToObservationLatency(labelValues, latency) - p.queryEndTimes.Delete(timestamp) } // Report latency for Observation() at end of call. defer func() { duration := float64(time.Now().UTC().Sub(start)) p.prometheusBackend.SetObservationDuration(labelValues, duration) - p.observationEndTimes.Store(timestamp, time.Now().UTC()) // note time at end of Observe() + p.setEndTime(timestamp, p.observationEndTimes) // note time at end of Observe() }() return p.wrapped.Observation(ctx, timestamp, query) @@ -270,17 +281,16 @@ func (p *promPlugin) Report(ctx context.Context, timestamp types.ReportTimestamp // Report latency between Observation() and Report(). labelValues := getLabelsValues(p, timestamp) - if observationEndTime, ok := p.observationEndTimes.Load(timestamp); ok { + if observationEndTime, ok := p.observationEndTimes.Get(timestampToKey(timestamp)); ok { latency := float64(start.Sub(observationEndTime.(time.Time))) p.prometheusBackend.SetObservationToReportLatency(labelValues, latency) - p.observationEndTimes.Delete(timestamp) } // Report latency for Report() at end of call. defer func() { duration := float64(time.Now().UTC().Sub(start)) p.prometheusBackend.SetReportDuration(labelValues, duration) - p.reportEndTimes.Store(timestamp, time.Now().UTC()) // note time at end of Report() + p.setEndTime(timestamp, p.reportEndTimes) // note time at end of Report() }() return p.wrapped.Report(ctx, timestamp, query, observations) @@ -291,17 +301,16 @@ func (p *promPlugin) ShouldAcceptFinalizedReport(ctx context.Context, timestamp // Report latency between Report() and ShouldAcceptFinalizedReport(). labelValues := getLabelsValues(p, timestamp) - if reportEndTime, ok := p.reportEndTimes.Load(timestamp); ok { + if reportEndTime, ok := p.reportEndTimes.Get(timestampToKey(timestamp)); ok { latency := float64(start.Sub(reportEndTime.(time.Time))) p.prometheusBackend.SetReportToAcceptFinalizedReportLatency(labelValues, latency) - p.reportEndTimes.Delete(timestamp) } // Report latency for ShouldAcceptFinalizedReport() at end of call. defer func() { duration := float64(time.Now().UTC().Sub(start)) p.prometheusBackend.SetShouldAcceptFinalizedReportDuration(labelValues, duration) - p.acceptFinalizedReportEndTimes.Store(timestamp, time.Now().UTC()) // note time at end of ShouldAcceptFinalizedReport() + p.setEndTime(timestamp, p.acceptFinalizedReportEndTimes) // note time at end of ShouldAcceptFinalizedReport() }() return p.wrapped.ShouldAcceptFinalizedReport(ctx, timestamp, report) @@ -312,10 +321,9 @@ func (p *promPlugin) ShouldTransmitAcceptedReport(ctx context.Context, timestamp // Report latency between ShouldAcceptFinalizedReport() and ShouldTransmitAcceptedReport(). labelValues := getLabelsValues(p, timestamp) - if acceptFinalizedReportEndTime, ok := p.acceptFinalizedReportEndTimes.Load(timestamp); ok { + if acceptFinalizedReportEndTime, ok := p.acceptFinalizedReportEndTimes.Get(timestampToKey(timestamp)); ok { latency := float64(start.Sub(acceptFinalizedReportEndTime.(time.Time))) p.prometheusBackend.SetAcceptFinalizedReportToTransmitAcceptedReportLatency(labelValues, latency) - p.acceptFinalizedReportEndTimes.Delete(timestamp) } defer func() { @@ -343,3 +351,11 @@ func (p *promPlugin) Close() error { return p.wrapped.Close() } + +func (p *promPlugin) setEndTime(timestamp types.ReportTimestamp, cache *cache.Cache) { + cache.SetDefault(timestampToKey(timestamp), time.Now().UTC()) +} + +func timestampToKey(timestamp types.ReportTimestamp) string { + return fmt.Sprintf("%x_%d_%d", timestamp.ConfigDigest[:], timestamp.Epoch, timestamp.Round) +} diff --git a/core/services/ocr2/plugins/promwrapper/plugin_test.go b/core/services/ocr2/plugins/promwrapper/plugin_test.go index b4de7f027f3..5b8187405fa 100644 --- a/core/services/ocr2/plugins/promwrapper/plugin_test.go +++ b/core/services/ocr2/plugins/promwrapper/plugin_test.go @@ -69,12 +69,12 @@ func TestPlugin_MustInstantiate(t *testing.T) { // Ensure instantiation without panic for no override backend. var reportingPlugin = &fakeReportingPlugin{} promPlugin := New(reportingPlugin, "test", "EVM", big.NewInt(1), types.ReportingPluginConfig{}, nil) - require.NotEqual(t, nil, promPlugin) + require.NotNil(t, promPlugin) // Ensure instantiation without panic for override provided. backend := mocks.NewPrometheusBackend(t) promPlugin = New(reportingPlugin, "test-2", "EVM", big.NewInt(1), types.ReportingPluginConfig{}, backend) - require.NotEqual(t, nil, promPlugin) + require.NotNil(t, promPlugin) } func TestPlugin_GetLatencies(t *testing.T) { @@ -194,45 +194,37 @@ func TestPlugin_GetLatencies(t *testing.T) { types.ReportingPluginConfig{ConfigDigest: reportTimestamp.ConfigDigest}, backend, ).(*promPlugin) - require.NotEqual(t, nil, promPlugin) + require.NotNil(t, promPlugin) ctx := testutils.Context(t) // Run OCR methods. _, err := promPlugin.Query(ctx, reportTimestamp) require.NoError(t, err) - _, ok := promPlugin.queryEndTimes.Load(reportTimestamp) - require.Equal(t, true, ok) + _, ok := promPlugin.queryEndTimes.Get(timestampToKey(reportTimestamp)) + require.True(t, ok) time.Sleep(qToOLatency) _, err = promPlugin.Observation(ctx, reportTimestamp, nil) require.NoError(t, err) - _, ok = promPlugin.queryEndTimes.Load(reportTimestamp) - require.Equal(t, false, ok) - _, ok = promPlugin.observationEndTimes.Load(reportTimestamp) - require.Equal(t, true, ok) + _, ok = promPlugin.observationEndTimes.Get(timestampToKey(reportTimestamp)) + require.True(t, ok) time.Sleep(oToRLatency) _, _, err = promPlugin.Report(ctx, reportTimestamp, nil, nil) require.NoError(t, err) - _, ok = promPlugin.observationEndTimes.Load(reportTimestamp) - require.Equal(t, false, ok) - _, ok = promPlugin.reportEndTimes.Load(reportTimestamp) - require.Equal(t, true, ok) + _, ok = promPlugin.reportEndTimes.Get(timestampToKey(reportTimestamp)) + require.True(t, ok) time.Sleep(rToALatency) _, err = promPlugin.ShouldAcceptFinalizedReport(ctx, reportTimestamp, nil) require.NoError(t, err) - _, ok = promPlugin.reportEndTimes.Load(reportTimestamp) - require.Equal(t, false, ok) - _, ok = promPlugin.acceptFinalizedReportEndTimes.Load(reportTimestamp) - require.Equal(t, true, ok) + _, ok = promPlugin.acceptFinalizedReportEndTimes.Get(timestampToKey(reportTimestamp)) + require.True(t, ok) time.Sleep(aToTLatency) _, err = promPlugin.ShouldTransmitAcceptedReport(ctx, reportTimestamp, nil) require.NoError(t, err) - _, ok = promPlugin.acceptFinalizedReportEndTimes.Load(reportTimestamp) - require.Equal(t, false, ok) // Close. err = promPlugin.Close() diff --git a/core/services/p2p/peer.go b/core/services/p2p/peer.go index 2be3cd9e9fd..fe800859b0c 100644 --- a/core/services/p2p/peer.go +++ b/core/services/p2p/peer.go @@ -232,5 +232,5 @@ func (p *peer) HealthReport() map[string]error { } func (p *peer) Name() string { - return "P2PPeer" + return p.lggr.Name() } diff --git a/core/services/p2p/wrapper/wrapper.go b/core/services/p2p/wrapper/wrapper.go index 7b5b92af72e..1f898b91d7d 100644 --- a/core/services/p2p/wrapper/wrapper.go +++ b/core/services/p2p/wrapper/wrapper.go @@ -36,7 +36,7 @@ func NewExternalPeerWrapper(keystoreP2P keystore.P2P, p2pConfig config.P2P, ds s return &peerWrapper{ keystoreP2P: keystoreP2P, p2pConfig: p2pConfig, - lggr: lggr, + lggr: lggr.Named("PeerWrapper"), ds: ds, } } @@ -126,7 +126,7 @@ func (e *peerWrapper) HealthReport() map[string]error { } func (e *peerWrapper) Name() string { - return "PeerWrapper" + return e.lggr.Name() } func (e *peerWrapper) Sign(msg []byte) ([]byte, error) { diff --git a/core/services/registrysyncer/monitoring.go b/core/services/registrysyncer/monitoring.go index 97d139d044f..97fd181515c 100644 --- a/core/services/registrysyncer/monitoring.go +++ b/core/services/registrysyncer/monitoring.go @@ -16,12 +16,12 @@ var remoteRegistrySyncFailureCounter metric.Int64Counter var launcherFailureCounter metric.Int64Counter func initMonitoringResources() (err error) { - remoteRegistrySyncFailureCounter, err = beholder.GetMeter().Int64Counter("RemoteRegistrySyncFailure") + remoteRegistrySyncFailureCounter, err = beholder.GetMeter().Int64Counter("platform_registrysyncer_sync_failures") if err != nil { return fmt.Errorf("failed to register sync failure counter: %w", err) } - launcherFailureCounter, err = beholder.GetMeter().Int64Counter("LauncherFailureCounter") + launcherFailureCounter, err = beholder.GetMeter().Int64Counter("platform_registrysyncer_launch_failures") if err != nil { return fmt.Errorf("failed to register launcher failure counter: %w", err) } diff --git a/core/services/registrysyncer/syncer.go b/core/services/registrysyncer/syncer.go index 40d105e2b8e..c0d2a60b47e 100644 --- a/core/services/registrysyncer/syncer.go +++ b/core/services/registrysyncer/syncer.go @@ -444,14 +444,10 @@ func (s *registrySyncer) Close() error { }) } -func (s *registrySyncer) Ready() error { - return nil -} - func (s *registrySyncer) HealthReport() map[string]error { - return nil + return map[string]error{s.Name(): s.Healthy()} } func (s *registrySyncer) Name() string { - return "RegistrySyncer" + return s.lggr.Name() } diff --git a/core/services/relay/evm/chain_writer.go b/core/services/relay/evm/chain_writer.go index 16eb8256cfa..779a13de90c 100644 --- a/core/services/relay/evm/chain_writer.go +++ b/core/services/relay/evm/chain_writer.go @@ -223,17 +223,11 @@ func (w *chainWriter) Close() error { } func (w *chainWriter) HealthReport() map[string]error { - return map[string]error{ - w.Name(): nil, - } + return map[string]error{w.Name(): w.Healthy()} } func (w *chainWriter) Name() string { - return "chain-writer" -} - -func (w *chainWriter) Ready() error { - return nil + return w.logger.Name() } func (w *chainWriter) Start(ctx context.Context) error { diff --git a/core/services/relay/evm/commit_provider.go b/core/services/relay/evm/commit_provider.go index 501e1ae0ef0..71ac3846395 100644 --- a/core/services/relay/evm/commit_provider.go +++ b/core/services/relay/evm/commit_provider.go @@ -3,6 +3,7 @@ package evm import ( "context" "fmt" + "math" "math/big" "go.uber.org/multierr" @@ -47,7 +48,7 @@ func NewSrcCommitProvider( maxGasPrice *big.Int, ) commontypes.CCIPCommitProvider { return &SrcCommitProvider{ - lggr: lggr, + lggr: logger.Named(lggr, "SrcCommitProvider"), startBlock: startBlock, client: client, lp: lp, @@ -84,7 +85,7 @@ func NewDstCommitProvider( configWatcher *configWatcher, ) commontypes.CCIPCommitProvider { return &DstCommitProvider{ - lggr: lggr, + lggr: logger.Named(lggr, "DstCommitProvider"), versionFinder: versionFinder, startBlock: startBlock, client: client, @@ -96,24 +97,24 @@ func NewDstCommitProvider( } } -func (P *SrcCommitProvider) Name() string { - return "CCIPCommitProvider.SrcRelayerProvider" +func (p *SrcCommitProvider) Name() string { + return p.lggr.Name() } // Close is called when the job that created this provider is deleted. // At this time, any of the methods on the provider may or may not have been called. // If NewOnRampReader has not been called, their corresponding // Close methods will be expected to error. -func (P *SrcCommitProvider) Close() error { +func (p *SrcCommitProvider) Close() error { versionFinder := ccip.NewEvmVersionFinder() unregisterFuncs := make([]func() error, 0, 2) unregisterFuncs = append(unregisterFuncs, func() error { // avoid panic in the case NewOnRampReader wasn't called - if P.seenOnRampAddress == nil { + if p.seenOnRampAddress == nil { return nil } - return ccip.CloseOnRampReader(P.lggr, versionFinder, *P.seenSourceChainSelector, *P.seenDestChainSelector, *P.seenOnRampAddress, P.lp, P.client) + return ccip.CloseOnRampReader(p.lggr, versionFinder, *p.seenSourceChainSelector, *p.seenDestChainSelector, *p.seenOnRampAddress, p.lp, p.client) }) var multiErr error @@ -125,59 +126,59 @@ func (P *SrcCommitProvider) Close() error { return multiErr } -func (P *SrcCommitProvider) Ready() error { +func (p *SrcCommitProvider) Ready() error { return nil } -func (P *SrcCommitProvider) HealthReport() map[string]error { - return make(map[string]error) +func (p *SrcCommitProvider) HealthReport() map[string]error { + return map[string]error{p.Name(): nil} } -func (P *SrcCommitProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { +func (p *SrcCommitProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { // TODO CCIP-2494 // "OffchainConfigDigester called on SrcCommitProvider. Valid on DstCommitProvider." return UnimplementedOffchainConfigDigester{} } -func (P *SrcCommitProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { +func (p *SrcCommitProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { // // TODO CCIP-2494 // "ContractConfigTracker called on SrcCommitProvider. Valid on DstCommitProvider.") return UnimplementedContractConfigTracker{} } -func (P *SrcCommitProvider) ContractTransmitter() ocrtypes.ContractTransmitter { +func (p *SrcCommitProvider) ContractTransmitter() ocrtypes.ContractTransmitter { // // TODO CCIP-2494 // "ContractTransmitter called on SrcCommitProvider. Valid on DstCommitProvider." return UnimplementedContractTransmitter{} } -func (P *SrcCommitProvider) ContractReader() commontypes.ContractReader { +func (p *SrcCommitProvider) ContractReader() commontypes.ContractReader { return nil } -func (P *SrcCommitProvider) Codec() commontypes.Codec { +func (p *SrcCommitProvider) Codec() commontypes.Codec { return nil } -func (P *DstCommitProvider) Name() string { - return "CCIPCommitProvider.DstRelayerProvider" +func (p *DstCommitProvider) Name() string { + return p.lggr.Name() } -func (P *DstCommitProvider) Close() error { +func (p *DstCommitProvider) Close() error { versionFinder := ccip.NewEvmVersionFinder() unregisterFuncs := make([]func() error, 0, 2) unregisterFuncs = append(unregisterFuncs, func() error { - if P.seenCommitStoreAddress == nil { + if p.seenCommitStoreAddress == nil { return nil } - return ccip.CloseCommitStoreReader(P.lggr, versionFinder, *P.seenCommitStoreAddress, P.client, P.lp) + return ccip.CloseCommitStoreReader(p.lggr, versionFinder, *p.seenCommitStoreAddress, p.client, p.lp) }) unregisterFuncs = append(unregisterFuncs, func() error { - if P.seenOffRampAddress == nil { + if p.seenOffRampAddress == nil { return nil } - return ccip.CloseOffRampReader(P.lggr, versionFinder, *P.seenOffRampAddress, P.client, P.lp, nil, big.NewInt(0)) + return ccip.CloseOffRampReader(p.lggr, versionFinder, *p.seenOffRampAddress, p.client, p.lp, nil, big.NewInt(0)) }) var multiErr error @@ -189,110 +190,116 @@ func (P *DstCommitProvider) Close() error { return multiErr } -func (P *DstCommitProvider) Ready() error { +func (p *DstCommitProvider) Ready() error { return nil } -func (P *DstCommitProvider) HealthReport() map[string]error { +func (p *DstCommitProvider) HealthReport() map[string]error { return make(map[string]error) } -func (P *DstCommitProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { - return P.configWatcher.OffchainConfigDigester() +func (p *DstCommitProvider) OffchainConfigDigester() ocrtypes.OffchainConfigDigester { + return p.configWatcher.OffchainConfigDigester() } -func (P *DstCommitProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { - return P.configWatcher.ContractConfigTracker() +func (p *DstCommitProvider) ContractConfigTracker() ocrtypes.ContractConfigTracker { + return p.configWatcher.ContractConfigTracker() } -func (P *DstCommitProvider) ContractTransmitter() ocrtypes.ContractTransmitter { - return P.contractTransmitter +func (p *DstCommitProvider) ContractTransmitter() ocrtypes.ContractTransmitter { + return p.contractTransmitter } -func (P *DstCommitProvider) ContractReader() commontypes.ContractReader { +func (p *DstCommitProvider) ContractReader() commontypes.ContractReader { return nil } -func (P *DstCommitProvider) Codec() commontypes.Codec { +func (p *DstCommitProvider) Codec() commontypes.Codec { return nil } -func (P *SrcCommitProvider) Start(ctx context.Context) error { - if P.startBlock != 0 { - P.lggr.Infow("start replaying src chain", "fromBlock", P.startBlock) - return P.lp.Replay(ctx, int64(P.startBlock)) +func (p *SrcCommitProvider) Start(ctx context.Context) error { + if p.startBlock != 0 { + p.lggr.Infow("start replaying src chain", "fromBlock", p.startBlock) + if p.startBlock > math.MaxInt64 { + return fmt.Errorf("start block overflows int64: %d", p.startBlock) + } + return p.lp.Replay(ctx, int64(p.startBlock)) //nolint:gosec // G115 false positive } return nil } -func (P *DstCommitProvider) Start(ctx context.Context) error { - if P.startBlock != 0 { - P.lggr.Infow("start replaying dst chain", "fromBlock", P.startBlock) - return P.lp.Replay(ctx, int64(P.startBlock)) +func (p *DstCommitProvider) Start(ctx context.Context) error { + if p.startBlock != 0 { + p.lggr.Infow("start replaying dst chain", "fromBlock", p.startBlock) + if p.startBlock > math.MaxInt64 { + return fmt.Errorf("start block overflows int64: %d", p.startBlock) + } + return p.lp.Replay(ctx, int64(p.startBlock)) //nolint:gosec // G115 false positive } return nil } -func (P *SrcCommitProvider) NewPriceGetter(ctx context.Context) (priceGetter cciptypes.PriceGetter, err error) { +func (p *SrcCommitProvider) NewPriceGetter(ctx context.Context) (priceGetter cciptypes.PriceGetter, err error) { return nil, fmt.Errorf("can't construct a price getter from one relayer") } -func (P *DstCommitProvider) NewPriceGetter(ctx context.Context) (priceGetter cciptypes.PriceGetter, err error) { +func (p *DstCommitProvider) NewPriceGetter(ctx context.Context) (priceGetter cciptypes.PriceGetter, err error) { return nil, fmt.Errorf("can't construct a price getter from one relayer") } -func (P *SrcCommitProvider) NewCommitStoreReader(ctx context.Context, commitStoreAddress cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { - commitStoreReader = NewIncompleteSourceCommitStoreReader(P.estimator, P.maxGasPrice) +func (p *SrcCommitProvider) NewCommitStoreReader(ctx context.Context, commitStoreAddress cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { + commitStoreReader = NewIncompleteSourceCommitStoreReader(p.estimator, p.maxGasPrice) return } -func (P *DstCommitProvider) NewCommitStoreReader(ctx context.Context, commitStoreAddress cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { - P.seenCommitStoreAddress = &commitStoreAddress +func (p *DstCommitProvider) NewCommitStoreReader(ctx context.Context, commitStoreAddress cciptypes.Address) (commitStoreReader cciptypes.CommitStoreReader, err error) { + p.seenCommitStoreAddress = &commitStoreAddress versionFinder := ccip.NewEvmVersionFinder() - commitStoreReader, err = NewIncompleteDestCommitStoreReader(P.lggr, versionFinder, commitStoreAddress, P.client, P.lp) + commitStoreReader, err = NewIncompleteDestCommitStoreReader(p.lggr, versionFinder, commitStoreAddress, p.client, p.lp) return } -func (P *SrcCommitProvider) NewOnRampReader(ctx context.Context, onRampAddress cciptypes.Address, sourceChainSelector uint64, destChainSelector uint64) (onRampReader cciptypes.OnRampReader, err error) { - P.seenOnRampAddress = &onRampAddress - P.seenSourceChainSelector = &sourceChainSelector - P.seenDestChainSelector = &destChainSelector +func (p *SrcCommitProvider) NewOnRampReader(ctx context.Context, onRampAddress cciptypes.Address, sourceChainSelector uint64, destChainSelector uint64) (onRampReader cciptypes.OnRampReader, err error) { + p.seenOnRampAddress = &onRampAddress + p.seenSourceChainSelector = &sourceChainSelector + p.seenDestChainSelector = &destChainSelector versionFinder := ccip.NewEvmVersionFinder() - onRampReader, err = ccip.NewOnRampReader(P.lggr, versionFinder, sourceChainSelector, destChainSelector, onRampAddress, P.lp, P.client) + onRampReader, err = ccip.NewOnRampReader(p.lggr, versionFinder, sourceChainSelector, destChainSelector, onRampAddress, p.lp, p.client) return } -func (P *DstCommitProvider) NewOnRampReader(ctx context.Context, onRampAddress cciptypes.Address, sourceChainSelector uint64, destChainSelector uint64) (onRampReader cciptypes.OnRampReader, err error) { +func (p *DstCommitProvider) NewOnRampReader(ctx context.Context, onRampAddress cciptypes.Address, sourceChainSelector uint64, destChainSelector uint64) (onRampReader cciptypes.OnRampReader, err error) { return nil, fmt.Errorf("invalid: NewOnRampReader called for DstCommitProvider.NewOnRampReader should be called on SrcCommitProvider") } -func (P *SrcCommitProvider) NewOffRampReader(ctx context.Context, offRampAddr cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { +func (p *SrcCommitProvider) NewOffRampReader(ctx context.Context, offRampAddr cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { return nil, fmt.Errorf("invalid: NewOffRampReader called for SrcCommitProvider. NewOffRampReader should be called on DstCommitProvider") } -func (P *DstCommitProvider) NewOffRampReader(ctx context.Context, offRampAddr cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { - offRampReader, err = ccip.NewOffRampReader(P.lggr, P.versionFinder, offRampAddr, P.client, P.lp, P.gasEstimator, &P.maxGasPrice, true) +func (p *DstCommitProvider) NewOffRampReader(ctx context.Context, offRampAddr cciptypes.Address) (offRampReader cciptypes.OffRampReader, err error) { + offRampReader, err = ccip.NewOffRampReader(p.lggr, p.versionFinder, offRampAddr, p.client, p.lp, p.gasEstimator, &p.maxGasPrice, true) return } -func (P *SrcCommitProvider) NewPriceRegistryReader(ctx context.Context, addr cciptypes.Address) (priceRegistryReader cciptypes.PriceRegistryReader, err error) { +func (p *SrcCommitProvider) NewPriceRegistryReader(ctx context.Context, addr cciptypes.Address) (priceRegistryReader cciptypes.PriceRegistryReader, err error) { return nil, fmt.Errorf("invalid: NewPriceRegistryReader called for SrcCommitProvider. NewOffRampReader should be called on DstCommitProvider") } -func (P *DstCommitProvider) NewPriceRegistryReader(ctx context.Context, addr cciptypes.Address) (priceRegistryReader cciptypes.PriceRegistryReader, err error) { - destPriceRegistry := ccip.NewEvmPriceRegistry(P.lp, P.client, P.lggr, ccip.CommitPluginLabel) +func (p *DstCommitProvider) NewPriceRegistryReader(ctx context.Context, addr cciptypes.Address) (priceRegistryReader cciptypes.PriceRegistryReader, err error) { + destPriceRegistry := ccip.NewEvmPriceRegistry(p.lp, p.client, p.lggr, ccip.CommitPluginLabel) priceRegistryReader, err = destPriceRegistry.NewPriceRegistryReader(ctx, addr) return } -func (P *SrcCommitProvider) SourceNativeToken(ctx context.Context, sourceRouterAddr cciptypes.Address) (cciptypes.Address, error) { +func (p *SrcCommitProvider) SourceNativeToken(ctx context.Context, sourceRouterAddr cciptypes.Address) (cciptypes.Address, error) { sourceRouterAddrHex, err := ccip.GenericAddrToEvm(sourceRouterAddr) if err != nil { return "", err } - sourceRouter, err := router.NewRouter(sourceRouterAddrHex, P.client) + sourceRouter, err := router.NewRouter(sourceRouterAddrHex, p.client) if err != nil { return "", err } @@ -304,6 +311,6 @@ func (P *SrcCommitProvider) SourceNativeToken(ctx context.Context, sourceRouterA return ccip.EvmAddrToGeneric(sourceNative), nil } -func (P *DstCommitProvider) SourceNativeToken(ctx context.Context, sourceRouterAddr cciptypes.Address) (cciptypes.Address, error) { +func (p *DstCommitProvider) SourceNativeToken(ctx context.Context, sourceRouterAddr cciptypes.Address) (cciptypes.Address, error) { return "", fmt.Errorf("invalid: SourceNativeToken called for DstCommitProvider. SourceNativeToken should be called on SrcCommitProvider") } diff --git a/core/services/relay/evm/exec_provider.go b/core/services/relay/evm/exec_provider.go index e0a9b378f27..98f85c23aa8 100644 --- a/core/services/relay/evm/exec_provider.go +++ b/core/services/relay/evm/exec_provider.go @@ -71,7 +71,7 @@ func NewSrcExecProvider( } return &SrcExecProvider{ - lggr: lggr, + lggr: logger.Named(lggr, "SrcExecProvider"), versionFinder: versionFinder, client: client, estimator: estimator, @@ -87,7 +87,7 @@ func NewSrcExecProvider( } func (s *SrcExecProvider) Name() string { - return "CCIP.SrcExecProvider" + return s.lggr.Name() } func (s *SrcExecProvider) Start(ctx context.Context) error { @@ -258,7 +258,7 @@ func NewDstExecProvider( offRampAddress cciptypes.Address, ) (commontypes.CCIPExecProvider, error) { return &DstExecProvider{ - lggr: lggr, + lggr: logger.Named(lggr, "DstExecProvider"), versionFinder: versionFinder, client: client, lp: lp, @@ -273,7 +273,7 @@ func NewDstExecProvider( } func (d *DstExecProvider) Name() string { - return "CCIP.DestRelayerExecProvider" + return d.lggr.Name() } func (d *DstExecProvider) Start(ctx context.Context) error { diff --git a/core/services/relay/evm/relayer_extender.go b/core/services/relay/evm/relayer_extender.go index 884597df718..0d01494169f 100644 --- a/core/services/relay/evm/relayer_extender.go +++ b/core/services/relay/evm/relayer_extender.go @@ -7,6 +7,8 @@ import ( "go.uber.org/multierr" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas/rollups" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml" "github.com/smartcontractkit/chainlink/v2/core/chains/legacyevm" ) @@ -55,6 +57,8 @@ func NewLegacyChains(ctx context.Context, opts legacyevm.ChainRelayOpts) (result } } + // map with lazy initialization for the txm to access evm clients for different chain + var clientsByChainID = make(map[string]rollups.DAClient) for i := range enabled { cid := enabled[i].ChainID.String() privOpts := legacyevm.ChainRelayOpts{ @@ -64,12 +68,13 @@ func NewLegacyChains(ctx context.Context, opts legacyevm.ChainRelayOpts) (result } privOpts.Logger.Infow(fmt.Sprintf("Loading chain %s", cid), "evmChainID", cid) - chain, err2 := legacyevm.NewTOMLChain(ctx, enabled[i], privOpts) + chain, err2 := legacyevm.NewTOMLChain(ctx, enabled[i], privOpts, clientsByChainID) if err2 != nil { err = multierr.Combine(err, fmt.Errorf("failed to create chain %s: %w", cid, err2)) continue } + clientsByChainID[cid] = chain.Client() result = append(result, chain) } return diff --git a/core/services/workflows/monitoring.go b/core/services/workflows/monitoring.go index 16679f60b4d..2389677036d 100644 --- a/core/services/workflows/monitoring.go +++ b/core/services/workflows/monitoring.go @@ -20,32 +20,32 @@ var workflowStepErrorCounter metric.Int64Counter var engineHeartbeatCounter metric.Int64UpDownCounter func initMonitoringResources() (err error) { - registerTriggerFailureCounter, err = beholder.GetMeter().Int64Counter("RegisterTriggerFailure") + registerTriggerFailureCounter, err = beholder.GetMeter().Int64Counter("platform_engine_registertrigger_failures") if err != nil { return fmt.Errorf("failed to register trigger failure counter: %w", err) } - workflowsRunningGauge, err = beholder.GetMeter().Int64Gauge("WorkflowsRunning") + workflowsRunningGauge, err = beholder.GetMeter().Int64Gauge("platform_engine_workflow_count") if err != nil { return fmt.Errorf("failed to register workflows running gauge: %w", err) } - capabilityInvocationCounter, err = beholder.GetMeter().Int64Counter("CapabilityInvocation") + capabilityInvocationCounter, err = beholder.GetMeter().Int64Counter("platform_engine_capabilities_count") if err != nil { return fmt.Errorf("failed to register capability invocation counter: %w", err) } - workflowExecutionLatencyGauge, err = beholder.GetMeter().Int64Gauge("WorkflowExecutionLatency") + workflowExecutionLatencyGauge, err = beholder.GetMeter().Int64Gauge("platform_engine_workflow_time") if err != nil { return fmt.Errorf("failed to register workflow execution latency gauge: %w", err) } - workflowStepErrorCounter, err = beholder.GetMeter().Int64Counter("WorkflowStepError") + workflowStepErrorCounter, err = beholder.GetMeter().Int64Counter("platform_engine_workflow_errors") if err != nil { return fmt.Errorf("failed to register workflow step error counter: %w", err) } - engineHeartbeatCounter, err = beholder.GetMeter().Int64UpDownCounter("EngineHeartbeat") + engineHeartbeatCounter, err = beholder.GetMeter().Int64UpDownCounter("platform_engine_heartbeat") if err != nil { return fmt.Errorf("failed to register engine heartbeat counter: %w", err) } diff --git a/core/web/eth_keys_controller_test.go b/core/web/eth_keys_controller_test.go index 9cb6a27b434..78982e2e304 100644 --- a/core/web/eth_keys_controller_test.go +++ b/core/web/eth_keys_controller_test.go @@ -33,7 +33,7 @@ func TestETHKeysController_Index_Success(t *testing.T) { ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -85,7 +85,7 @@ func TestETHKeysController_Index_Errors(t *testing.T) { ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -158,7 +158,7 @@ func TestETHKeysController_Index_NotDev(t *testing.T) { t.Parallel() ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -227,6 +227,7 @@ func TestETHKeysController_CreateSuccess(t *testing.T) { ethClient.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Return(ethBalanceInt, nil) linkBalance := assets.NewLinkFromJuels(42) ethClient.On("LINKBalance", mock.Anything, mock.Anything, mock.Anything).Return(linkBalance, nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() client := app.NewHTTPClient(nil) @@ -257,7 +258,7 @@ func TestETHKeysController_ChainSuccess_UpdateNonce(t *testing.T) { ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -300,7 +301,7 @@ func TestETHKeysController_ChainSuccess_Disable(t *testing.T) { ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -391,7 +392,7 @@ func TestETHKeysController_ChainSuccess_ResetWithAbandon(t *testing.T) { ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -464,7 +465,7 @@ func TestETHKeysController_ChainFailure_InvalidAbandon(t *testing.T) { ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -498,7 +499,7 @@ func TestETHKeysController_ChainFailure_InvalidEnabled(t *testing.T) { ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -619,7 +620,7 @@ func TestETHKeysController_ChainFailure_MissingChainID(t *testing.T) { ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -651,7 +652,7 @@ func TestETHKeysController_DeleteSuccess(t *testing.T) { t.Parallel() ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Twice() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].NonceAutoSync = ptr(false) c.EVM[0].BalanceMonitor.Enabled = ptr(false) diff --git a/core/web/evm_transfer_controller_test.go b/core/web/evm_transfer_controller_test.go index 61226839401..55a55399405 100644 --- a/core/web/evm_transfer_controller_test.go +++ b/core/web/evm_transfer_controller_test.go @@ -41,8 +41,9 @@ func TestTransfersController_CreateSuccess_From(t *testing.T) { balance, err := assets.NewEthValueS("200") require.NoError(t, err) - ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil) + ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil).Maybe() ethClient.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := cltest.NewApplicationWithKey(t, ethClient, key) require.NoError(t, app.Start(testutils.Context(t))) @@ -83,8 +84,9 @@ func TestTransfersController_CreateSuccess_From_WEI(t *testing.T) { balance, err := assets.NewEthValueS("2") require.NoError(t, err) - ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil) + ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil).Maybe() ethClient.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := cltest.NewApplicationWithKey(t, ethClient, key) require.NoError(t, app.Start(testutils.Context(t))) @@ -124,8 +126,9 @@ func TestTransfersController_CreateSuccess_From_BalanceMonitorDisabled(t *testin balance, err := assets.NewEthValueS("200") require.NoError(t, err) - ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil) + ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil).Maybe() ethClient.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].BalanceMonitor.Enabled = ptr(false) @@ -193,8 +196,9 @@ func TestTransfersController_TransferBalanceToLowError(t *testing.T) { ethClient := cltest.NewEthMocksWithTransactionsOnBlocksAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil) + ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil).Maybe() ethClient.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(assets.NewEth(10).ToInt(), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := cltest.NewApplicationWithKey(t, ethClient, key) require.NoError(t, app.Start(testutils.Context(t))) @@ -231,8 +235,9 @@ func TestTransfersController_TransferBalanceToLowError_ZeroBalance(t *testing.T) balance, err := assets.NewEthValueS("0") require.NoError(t, err) - ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil) + ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil).Maybe() ethClient.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() app := cltest.NewApplicationWithKey(t, ethClient, key) require.NoError(t, app.Start(testutils.Context(t))) @@ -285,7 +290,7 @@ func TestTransfersController_CreateSuccess_eip1559(t *testing.T) { ethClient.On("PendingNonceAt", mock.Anything, key.Address).Return(uint64(1), nil) ethClient.On("BalanceAt", mock.Anything, key.Address, (*big.Int)(nil)).Return(balance.ToInt(), nil) - ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Maybe() + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil) config := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.EVM[0].GasEstimator.EIP1559DynamicFees = ptr(true) diff --git a/core/web/jobs_controller_test.go b/core/web/jobs_controller_test.go index 60abe61537f..f0c8f4b76e7 100644 --- a/core/web/jobs_controller_test.go +++ b/core/web/jobs_controller_test.go @@ -801,6 +801,7 @@ func setupJobsControllerTests(t *testing.T) (ta *cltest.TestApplication, cc clte func setupEthClientForControllerTests(t *testing.T) *evmclimocks.Client { ec := cltest.NewEthMocksWithStartupAssertions(t) ec.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil).Maybe() + ec.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() ec.On("LatestBlockHeight", mock.Anything).Return(big.NewInt(100), nil).Maybe() ec.On("BalanceAt", mock.Anything, mock.Anything, mock.Anything).Once().Return(big.NewInt(0), nil).Maybe() return ec diff --git a/core/web/pipeline_runs_controller_test.go b/core/web/pipeline_runs_controller_test.go index e123df2bdb3..738d17de600 100644 --- a/core/web/pipeline_runs_controller_test.go +++ b/core/web/pipeline_runs_controller_test.go @@ -254,7 +254,7 @@ func setupPipelineRunsControllerTests(t *testing.T) (cltest.HTTPClientCleaner, i t.Parallel() ctx := testutils.Context(t) ethClient := cltest.NewEthMocksWithStartupAssertions(t) - ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) + ethClient.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(uint64(0), nil).Once() cfg := configtest.NewGeneralConfig(t, func(c *chainlink.Config, s *chainlink.Secrets) { c.OCR.Enabled = ptr(true) c.P2P.V2.Enabled = ptr(true) diff --git a/core/web/resolver/spec.go b/core/web/resolver/spec.go index 00b2442acab..ce23df49264 100644 --- a/core/web/resolver/spec.go +++ b/core/web/resolver/spec.go @@ -1,6 +1,8 @@ package resolver import ( + "fmt" + "github.com/graph-gophers/graphql-go" "github.com/smartcontractkit/chainlink/v2/core/services/job" @@ -133,6 +135,14 @@ func (r *SpecResolver) ToStandardCapabilitiesSpec() (*StandardCapabilitiesSpecRe return &StandardCapabilitiesSpecResolver{spec: *r.j.StandardCapabilitiesSpec}, true } +func (r *SpecResolver) ToStreamSpec() (*StreamSpecResolver, bool) { + if r.j.Type != job.Stream { + return nil, false + } + + return &StreamSpecResolver{streamID: fmt.Sprintf("%d", r.j.StreamID)}, true +} + type CronSpecResolver struct { spec job.CronSpec } @@ -1045,3 +1055,11 @@ func (r *StandardCapabilitiesSpecResolver) Command() string { func (r *StandardCapabilitiesSpecResolver) Config() *string { return &r.spec.Config } + +type StreamSpecResolver struct { + streamID string +} + +func (r *StreamSpecResolver) StreamID() string { + return r.streamID +} diff --git a/core/web/schema/type/spec.graphql b/core/web/schema/type/spec.graphql index 5a803e2f8ee..db81001543c 100644 --- a/core/web/schema/type/spec.graphql +++ b/core/web/schema/type/spec.graphql @@ -12,7 +12,8 @@ union JobSpec = BootstrapSpec | GatewaySpec | WorkflowSpec | - StandardCapabilitiesSpec + StandardCapabilitiesSpec | + StreamSpec type CronSpec { schedule: String! @@ -178,4 +179,8 @@ type StandardCapabilitiesSpec { createdAt: Time! command: String! config: String -} \ No newline at end of file +} + +type StreamSpec { + streamID: String! +} diff --git a/deployment/ccip/active_candidate.go b/deployment/ccip/active_candidate.go new file mode 100644 index 00000000000..c65dac04103 --- /dev/null +++ b/deployment/ccip/active_candidate.go @@ -0,0 +1,139 @@ +package ccipdeployment + +import ( + "fmt" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/chainlink/deployment" + 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" + "math/big" +) + +// 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 := 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 := 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: 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 := 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: 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 := 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/add_lane_test.go b/deployment/ccip/add_lane_test.go index 4b6f7671349..de47aa8e627 100644 --- a/deployment/ccip/add_lane_test.go +++ b/deployment/ccip/add_lane_test.go @@ -20,7 +20,7 @@ func TestAddLane(t *testing.T) { // We add more chains to the chainlink nodes than the number of chains where CCIP is deployed. e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 4, 4) // Here we have CR + nodes set up, but no CCIP contracts deployed. - state, err := LoadOnchainState(e.Env, e.Ab) + state, err := LoadOnchainState(e.Env) require.NoError(t, err) selectors := e.Env.AllChainSelectors() @@ -30,24 +30,21 @@ func TestAddLane(t *testing.T) { feeds := state.Chains[e.FeedChainSel].USDFeeds tokenConfig := NewTestTokenConfig(feeds) - feeTokenContracts := make(map[uint64]FeeTokenContracts) - for _, sel := range []uint64{chain1, chain2} { - feeTokenContracts[sel] = e.FeeTokenContracts[sel] - } // Set up CCIP contracts and a DON per chain. - err = DeployCCIPContracts(e.Env, e.Ab, DeployCCIPContractConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - TokenConfig: tokenConfig, - MCMSConfig: NewTestMCMSConfig(t, e.Env), - ExistingAddressBook: e.Ab, - ChainsToDeploy: []uint64{chain1, chain2}, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + newAddresses := deployment.NewMemoryAddressBook() + err = DeployCCIPContracts(e.Env, newAddresses, DeployCCIPContractConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + TokenConfig: tokenConfig, + MCMSConfig: NewTestMCMSConfig(t, e.Env), + ChainsToDeploy: []uint64{chain1, chain2}, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) // We expect no lanes available on any chain. - state, err = LoadOnchainState(e.Env, e.Ab) + state, err = LoadOnchainState(e.Env) require.NoError(t, err) for _, sel := range []uint64{chain1, chain2} { chain := state.Chains[sel] diff --git a/deployment/ccip/changeset/active_candidate.go b/deployment/ccip/changeset/active_candidate.go index bf061224561..7d09f81049b 100644 --- a/deployment/ccip/changeset/active_candidate.go +++ b/deployment/ccip/changeset/active_candidate.go @@ -2,171 +2,47 @@ package changeset import ( "fmt" - "math/big" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" 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 := ccdeploy.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 := ccdeploy.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: ccdeploy.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 := ccdeploy.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: ccdeploy.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 := ccdeploy.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 -} - -// PromoteAllCandidatesProposal generates a proposal to call promoteCandidate on the CCIPHome through CapReg. +// PromoteAllCandidatesChangeset generates a proposal to call promoteCandidate on the CCIPHome through CapReg. // This needs to be called after SetCandidateProposal is executed. -func PromoteAllCandidatesProposal( +func PromoteAllCandidatesChangeset( state ccdeploy.CCIPOnChainState, homeChainSel, newChainSel uint64, nodes deployment.Nodes, -) (*timelock.MCMSWithTimelockProposal, error) { - promoteCandidateOps, err := PromoteAllCandidatesForChainOps( +) (deployment.ChangesetOutput, error) { + promoteCandidateOps, err := ccdeploy.PromoteAllCandidatesForChainOps( state.Chains[homeChainSel].CapabilityRegistry, state.Chains[homeChainSel].CCIPHome, newChainSel, nodes.NonBootstraps(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } - return ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + prop, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(homeChainSel), Batch: promoteCandidateOps, }}, "promoteCandidate for commit and execution", 0) + 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. -func SetCandidatePluginProposal( +func SetCandidatePluginChangeset( state ccdeploy.CCIPOnChainState, e deployment.Environment, nodes deployment.Nodes, @@ -174,7 +50,7 @@ func SetCandidatePluginProposal( homeChainSel, feedChainSel, newChainSel uint64, tokenConfig ccdeploy.TokenConfig, pluginType cctypes.PluginType, -) (*timelock.MCMSWithTimelockProposal, error) { +) (deployment.ChangesetOutput, error) { newDONArgs, err := ccdeploy.BuildOCR3ConfigForCCIPHome( e.Logger, ocrSecrets, @@ -186,15 +62,15 @@ func SetCandidatePluginProposal( state.Chains[homeChainSel].RMNHome.Address(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } execConfig, ok := newDONArgs[pluginType] if !ok { - return nil, fmt.Errorf("missing exec plugin in ocr3Configs") + return deployment.ChangesetOutput{}, fmt.Errorf("missing exec plugin in ocr3Configs") } - setCandidateMCMSOps, err := SetCandidateOnExistingDon( + setCandidateMCMSOps, err := ccdeploy.SetCandidateOnExistingDon( execConfig, state.Chains[homeChainSel].CapabilityRegistry, state.Chains[homeChainSel].CCIPHome, @@ -202,11 +78,20 @@ func SetCandidatePluginProposal( nodes.NonBootstraps(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } - return ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + prop, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(homeChainSel), Batch: setCandidateMCMSOps, }}, "SetCandidate for execution", 0) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{ + *prop, + }, + }, nil + } diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index 28a3e67bc66..a91b2104a60 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -10,9 +10,6 @@ import ( cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - "github.com/smartcontractkit/chainlink/deployment" "github.com/stretchr/testify/require" @@ -32,41 +29,25 @@ func TestActiveCandidate(t *testing.T) { tenv := ccdeploy.NewMemoryEnvironment(t, lggr, 3, 5) e := tenv.Env - state, err := ccdeploy.LoadOnchainState(tenv.Env, tenv.Ab) + state, err := ccdeploy.LoadOnchainState(tenv.Env) require.NoError(t, err) require.NotNil(t, state.Chains[tenv.HomeChainSel].LinkToken) feeds := state.Chains[tenv.FeedChainSel].USDFeeds - tokenConfig := ccdeploy.NewTokenConfig() - - tokenConfig.UpsertTokenInfo(ccdeploy.LinkSymbol, - pluginconfig.TokenInfo{ - AggregatorAddress: cciptypes.UnknownEncodedAddress(feeds[ccdeploy.LinkSymbol].Address().String()), - Decimals: ccdeploy.LinkDecimals, - DeviationPPB: cciptypes.NewBigIntFromInt64(1e9), - }, - ) - tokenConfig.UpsertTokenInfo(ccdeploy.WethSymbol, - pluginconfig.TokenInfo{ - AggregatorAddress: cciptypes.UnknownEncodedAddress(feeds[ccdeploy.WethSymbol].Address().String()), - Decimals: ccdeploy.WethDecimals, - DeviationPPB: cciptypes.NewBigIntFromInt64(4e9), - }, - ) + tokenConfig := ccdeploy.NewTestTokenConfig(feeds) output, err := InitialDeploy(tenv.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: tokenConfig, - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), - ExistingAddressBook: tenv.Ab, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + HomeChainSel: tenv.HomeChainSel, + FeedChainSel: tenv.FeedChainSel, + ChainsToDeploy: tenv.Env.AllChainSelectors(), + TokenConfig: tokenConfig, + MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) // Get new state after migration. - require.NoError(t, tenv.Ab.Merge(output.AddressBook)) - state, err = ccdeploy.LoadOnchainState(e, tenv.Ab) + require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) + state, err = ccdeploy.LoadOnchainState(tenv.Env) require.NoError(t, err) homeCS, destCS := tenv.HomeChainSel, tenv.FeedChainSel @@ -143,7 +124,7 @@ func TestActiveCandidate(t *testing.T) { require.Equal(t, 5, len(donInfo.NodeP2PIds)) require.Equal(t, uint32(4), donInfo.ConfigCount) - state, err = ccdeploy.LoadOnchainState(e, tenv.Ab) + state, err = ccdeploy.LoadOnchainState(e) require.NoError(t, err) // delete a non-bootstrap node @@ -175,7 +156,7 @@ func TestActiveCandidate(t *testing.T) { ) require.NoError(t, err) - setCommitCandidateOp, err := SetCandidateOnExistingDon( + setCommitCandidateOp, err := ccdeploy.SetCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPCommit], state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, @@ -192,7 +173,7 @@ func TestActiveCandidate(t *testing.T) { ccdeploy.ExecuteProposal(t, e, setCommitCandidateSigned, state, homeCS) // create the op for the commit plugin as well - setExecCandidateOp, err := SetCandidateOnExistingDon( + setExecCandidateOp, err := ccdeploy.SetCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPExec], state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, @@ -226,7 +207,7 @@ func TestActiveCandidate(t *testing.T) { oldCandidateDigest, err := state.Chains[homeCS].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) require.NoError(t, err) - promoteOps, err := PromoteAllCandidatesForChainOps(state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, destCS, nodes.NonBootstraps()) + promoteOps, err := ccdeploy.PromoteAllCandidatesForChainOps(state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, destCS, nodes.NonBootstraps()) require.NoError(t, err) promoteProposal, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(homeCS), diff --git a/deployment/ccip/changeset/add_chain.go b/deployment/ccip/changeset/add_chain.go index 7f0b096f0a9..9742de64bc6 100644 --- a/deployment/ccip/changeset/add_chain.go +++ b/deployment/ccip/changeset/add_chain.go @@ -16,15 +16,15 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" ) -// NewChainInboundProposal generates a proposal +// NewChainInboundChangeset generates a proposal // to connect the new chain to the existing chains. -func NewChainInboundProposal( +func NewChainInboundChangeset( e deployment.Environment, state ccipdeployment.CCIPOnChainState, homeChainSel uint64, newChainSel uint64, sources []uint64, -) (*timelock.MCMSWithTimelockProposal, error) { +) (deployment.ChangesetOutput, error) { // Generate proposal which enables new destination (from test router) on all source chains. var batches []timelock.BatchChainOperation for _, source := range sources { @@ -35,7 +35,7 @@ func NewChainInboundProposal( }, }) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } enableFeeQuoterDest, err := state.Chains[source].FeeQuoter.ApplyDestChainConfigUpdates( deployment.SimTransactOpts(), @@ -46,7 +46,7 @@ func NewChainInboundProposal( }, }) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } batches = append(batches, timelock.BatchChainOperation{ ChainIdentifier: mcms.ChainIdentifier(source), @@ -68,7 +68,7 @@ func NewChainInboundProposal( addChainOp, err := ccipdeployment.ApplyChainConfigUpdatesOp(e, state, homeChainSel, []uint64{newChainSel}) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } batches = append(batches, timelock.BatchChainOperation{ @@ -78,12 +78,19 @@ func NewChainInboundProposal( }, }) - return ccipdeployment.BuildProposalFromBatches(state, batches, "proposal to set new chains", 0) + prop, err := ccipdeployment.BuildProposalFromBatches(state, batches, "proposal to set new chains", 0) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil } -// AddDonAndSetCandidateProposal adds new DON for destination to home chain +// AddDonAndSetCandidateChangeset adds new DON for destination to home chain // and sets the commit plugin config as candidateConfig for the don. -func AddDonAndSetCandidateProposal( +func AddDonAndSetCandidateChangeset( state ccipdeployment.CCIPOnChainState, e deployment.Environment, nodes deployment.Nodes, @@ -91,7 +98,7 @@ func AddDonAndSetCandidateProposal( homeChainSel, feedChainSel, newChainSel uint64, tokenConfig ccipdeployment.TokenConfig, pluginType types.PluginType, -) (*timelock.MCMSWithTimelockProposal, error) { +) (deployment.ChangesetOutput, error) { newDONArgs, err := ccipdeployment.BuildOCR3ConfigForCCIPHome( e.Logger, ocrSecrets, @@ -103,15 +110,15 @@ func AddDonAndSetCandidateProposal( state.Chains[homeChainSel].RMNHome.Address(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } latestDon, err := ccipdeployment.LatestCCIPDON(state.Chains[homeChainSel].CapabilityRegistry) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } commitConfig, ok := newDONArgs[pluginType] if !ok { - return nil, fmt.Errorf("missing commit plugin in ocr3Configs") + return deployment.ChangesetOutput{}, fmt.Errorf("missing commit plugin in ocr3Configs") } donID := latestDon.Id + 1 addDonOp, err := ccipdeployment.NewDonWithCandidateOp( @@ -120,11 +127,18 @@ func AddDonAndSetCandidateProposal( nodes.NonBootstraps(), ) if err != nil { - return nil, err + return deployment.ChangesetOutput{}, err } - return ccipdeployment.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + prop, err := ccipdeployment.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(homeChainSel), Batch: []mcms.Operation{addDonOp}, }}, "setCandidate for commit and AddDon on new Chain", 0) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil } diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 16dec2d6ce4..8149b6e1b0b 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -29,7 +29,7 @@ import ( func TestAddChainInbound(t *testing.T) { // 4 chains where the 4th is added after initial deployment. e := ccipdeployment.NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 4, 4) - state, err := ccipdeployment.LoadOnchainState(e.Env, e.Ab) + state, err := ccipdeployment.LoadOnchainState(e.Env) require.NoError(t, err) // Take first non-home chain as the new chain. newChain := e.Env.AllChainSelectorsExcluding([]uint64{e.HomeChainSel})[0] @@ -37,17 +37,18 @@ func TestAddChainInbound(t *testing.T) { initialDeploy := e.Env.AllChainSelectorsExcluding([]uint64{newChain}) tokenConfig := ccipdeployment.NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) - err = ccipdeployment.DeployCCIPContracts(e.Env, e.Ab, ccipdeployment.DeployCCIPContractConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainsToDeploy: initialDeploy, - TokenConfig: tokenConfig, - MCMSConfig: ccipdeployment.NewTestMCMSConfig(t, e.Env), - ExistingAddressBook: e.Ab, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + newAddresses := deployment.NewMemoryAddressBook() + err = ccipdeployment.DeployCCIPContracts(e.Env, newAddresses, ccipdeployment.DeployCCIPContractConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainsToDeploy: initialDeploy, + TokenConfig: tokenConfig, + MCMSConfig: ccipdeployment.NewTestMCMSConfig(t, e.Env), + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) - state, err = ccipdeployment.LoadOnchainState(e.Env, e.Ab) + require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) + state, err = ccipdeployment.LoadOnchainState(e.Env) require.NoError(t, err) // Connect all the existing lanes. @@ -59,16 +60,23 @@ func TestAddChainInbound(t *testing.T) { } } - rmnHomeAddress, err := deployment.SearchAddressBook(e.Ab, e.HomeChainSel, ccipdeployment.RMNHome) + rmnHomeAddress, err := deployment.SearchAddressBook(e.Env.ExistingAddresses, e.HomeChainSel, ccipdeployment.RMNHome) require.NoError(t, err) require.True(t, common.IsHexAddress(rmnHomeAddress)) rmnHome, err := rmn_home.NewRMNHome(common.HexToAddress(rmnHomeAddress), e.Env.Chains[e.HomeChainSel].Client) require.NoError(t, err) // Deploy contracts to new chain - err = ccipdeployment.DeployChainContracts(e.Env, e.Env.Chains[newChain], e.Ab, e.FeeTokenContracts[newChain], ccipdeployment.NewTestMCMSConfig(t, e.Env), rmnHome) + newChainAddresses := deployment.NewMemoryAddressBook() + err = ccipdeployment.DeployChainContracts(e.Env, + e.Env.Chains[newChain], newChainAddresses, + ccipdeployment.FeeTokenContracts{ + LinkToken: state.Chains[newChain].LinkToken, + Weth9: state.Chains[newChain].Weth9, + }, ccipdeployment.NewTestMCMSConfig(t, e.Env), rmnHome) require.NoError(t, err) - state, err = ccipdeployment.LoadOnchainState(e.Env, e.Ab) + require.NoError(t, e.Env.ExistingAddresses.Merge(newChainAddresses)) + state, err = ccipdeployment.LoadOnchainState(e.Env) require.NoError(t, err) // Transfer onramp/fq ownership to timelock. @@ -124,34 +132,28 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) // Generate and sign inbound proposal to new 4th chain. - chainInboundProposal, err := NewChainInboundProposal(e.Env, state, e.HomeChainSel, newChain, initialDeploy) + chainInboundChangeset, err := NewChainInboundChangeset(e.Env, state, e.HomeChainSel, newChain, initialDeploy) require.NoError(t, err) - chainInboundExec := ccipdeployment.SignProposal(t, e.Env, chainInboundProposal) - for _, sel := range initialDeploy { - ccipdeployment.ExecuteProposal(t, e.Env, chainInboundExec, state, sel) - } + ccipdeployment.ProcessChangeset(t, e.Env, chainInboundChangeset) + // TODO This currently is not working - Able to send the request here but request gets stuck in execution // Send a new message and expect that this is delivered once the chain is completely set up as inbound //TestSendRequest(t, e.Env, state, initialDeploy[0], newChain, true) t.Logf("Executing add don and set candidate proposal for commit plugin on chain %d", newChain) - addDonProp, err := AddDonAndSetCandidateProposal(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPCommit) + addDonChangeset, err := AddDonAndSetCandidateChangeset(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPCommit) require.NoError(t, err) - - addDonExec := ccipdeployment.SignProposal(t, e.Env, addDonProp) - ccipdeployment.ExecuteProposal(t, e.Env, addDonExec, state, e.HomeChainSel) + ccipdeployment.ProcessChangeset(t, e.Env, addDonChangeset) t.Logf("Executing promote candidate proposal for exec plugin on chain %d", newChain) - setCandidateForExecProposal, err := SetCandidatePluginProposal(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPExec) + setCandidateForExecChangeset, err := SetCandidatePluginChangeset(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPExec) require.NoError(t, err) - setCandidateForExecExec := ccipdeployment.SignProposal(t, e.Env, setCandidateForExecProposal) - ccipdeployment.ExecuteProposal(t, e.Env, setCandidateForExecExec, state, e.HomeChainSel) + ccipdeployment.ProcessChangeset(t, e.Env, setCandidateForExecChangeset) t.Logf("Executing promote candidate proposal for both commit and exec plugins on chain %d", newChain) - donPromoteProposal, err := PromoteAllCandidatesProposal(state, e.HomeChainSel, newChain, nodes) + donPromoteChangeset, err := PromoteAllCandidatesChangeset(state, e.HomeChainSel, newChain, nodes) require.NoError(t, err) - donPromoteExec := ccipdeployment.SignProposal(t, e.Env, donPromoteProposal) - ccipdeployment.ExecuteProposal(t, e.Env, donPromoteExec, state, e.HomeChainSel) + ccipdeployment.ProcessChangeset(t, e.Env, donPromoteChangeset) // verify if the configs are updated require.NoError(t, ccipdeployment.ValidateCCIPHomeConfigSetUp( @@ -187,7 +189,7 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) // Assert the inbound lanes to the new chain are wired correctly. - state, err = ccipdeployment.LoadOnchainState(e.Env, e.Ab) + state, err = ccipdeployment.LoadOnchainState(e.Env) require.NoError(t, err) for _, chain := range initialDeploy { cfg, err2 := state.Chains[chain].OnRamp.GetDestChainConfig(nil, newChain) diff --git a/deployment/ccip/changeset/initial_deploy.go b/deployment/ccip/changeset/initial_deploy.go index 23f517877b5..f9d7caf44a3 100644 --- a/deployment/ccip/changeset/initial_deploy.go +++ b/deployment/ccip/changeset/initial_deploy.go @@ -15,19 +15,19 @@ func InitialDeploy(env deployment.Environment, config interface{}) (deployment.C if !ok { return deployment.ChangesetOutput{}, deployment.ErrInvalidConfig } - ab := deployment.NewMemoryAddressBook() - err := ccipdeployment.DeployCCIPContracts(env, ab, c) + newAddresses := deployment.NewMemoryAddressBook() + err := ccipdeployment.DeployCCIPContracts(env, newAddresses, c) if err != nil { - env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "addresses", ab) - return deployment.ChangesetOutput{AddressBook: ab}, deployment.MaybeDataErr(err) + env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "newAddresses", newAddresses) + return deployment.ChangesetOutput{AddressBook: newAddresses}, deployment.MaybeDataErr(err) } js, err := ccipdeployment.NewCCIPJobSpecs(env.NodeIDs, env.Offchain) if err != nil { - return deployment.ChangesetOutput{AddressBook: ab}, err + return deployment.ChangesetOutput{AddressBook: newAddresses}, err } return deployment.ChangesetOutput{ Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: ab, + AddressBook: newAddresses, // Mapping of which nodes get which jobs. JobSpecs: js, }, nil diff --git a/deployment/ccip/changeset/initial_deploy_test.go b/deployment/ccip/changeset/initial_deploy_test.go index b220508b99e..65b6bf51e62 100644 --- a/deployment/ccip/changeset/initial_deploy_test.go +++ b/deployment/ccip/changeset/initial_deploy_test.go @@ -22,23 +22,22 @@ func TestInitialDeploy(t *testing.T) { tenv := ccdeploy.NewMemoryEnvironment(t, lggr, 3, 4) e := tenv.Env - state, err := ccdeploy.LoadOnchainState(tenv.Env, tenv.Ab) + state, err := ccdeploy.LoadOnchainState(tenv.Env) require.NoError(t, err) require.NotNil(t, state.Chains[tenv.HomeChainSel].LinkToken) output, err := InitialDeploy(tenv.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: ccdeploy.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), - ExistingAddressBook: tenv.Ab, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + HomeChainSel: tenv.HomeChainSel, + FeedChainSel: tenv.FeedChainSel, + ChainsToDeploy: tenv.Env.AllChainSelectors(), + TokenConfig: ccdeploy.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), + MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) // Get new state after migration. - require.NoError(t, tenv.Ab.Merge(output.AddressBook)) - state, err = ccdeploy.LoadOnchainState(e, tenv.Ab) + require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) + state, err = ccdeploy.LoadOnchainState(e) require.NoError(t, err) // Ensure capreg logs are up to date. diff --git a/deployment/ccip/changeset/view.go b/deployment/ccip/changeset/view.go index 4524d2f7fd0..9d3eb8260c7 100644 --- a/deployment/ccip/changeset/view.go +++ b/deployment/ccip/changeset/view.go @@ -11,8 +11,8 @@ import ( var _ deployment.ViewState = ViewCCIP -func ViewCCIP(e deployment.Environment, ab deployment.AddressBook) (json.Marshaler, error) { - state, err := ccipdeployment.LoadOnchainState(e, ab) +func ViewCCIP(e deployment.Environment) (json.Marshaler, error) { + state, err := ccipdeployment.LoadOnchainState(e) if err != nil { return nil, err } diff --git a/deployment/ccip/deploy.go b/deployment/ccip/deploy.go index c3952c22156..a0c61cd5f96 100644 --- a/deployment/ccip/deploy.go +++ b/deployment/ccip/deploy.go @@ -118,11 +118,10 @@ func deployContract[C Contracts]( } type DeployCCIPContractConfig struct { - HomeChainSel uint64 - FeedChainSel uint64 - ChainsToDeploy []uint64 - TokenConfig TokenConfig - ExistingAddressBook deployment.AddressBook + HomeChainSel uint64 + FeedChainSel uint64 + ChainsToDeploy []uint64 + TokenConfig TokenConfig // I believe it makes sense to have the same signers across all chains // since that's the point MCMS. MCMSConfig MCMSConfig @@ -148,7 +147,7 @@ func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c e.Logger.Errorw("Failed to get node info", "err", err) return err } - existingState, err := LoadOnchainState(e, c.ExistingAddressBook) + existingState, err := LoadOnchainState(e) if err != nil { e.Logger.Errorw("Failed to load existing onchain state", "err") return err @@ -364,16 +363,14 @@ func DeployMCMSContracts( }, nil } -func DeployFeeTokensToChains(lggr logger.Logger, ab deployment.AddressBook, chains map[uint64]deployment.Chain) (map[uint64]FeeTokenContracts, error) { - feeTokenContractsByChain := make(map[uint64]FeeTokenContracts) +func DeployFeeTokensToChains(lggr logger.Logger, ab deployment.AddressBook, chains map[uint64]deployment.Chain) error { for _, chain := range chains { - feeTokenContracts, err := DeployFeeTokens(lggr, chain, ab) + _, err := DeployFeeTokens(lggr, chain, ab) if err != nil { - return nil, err + return err } - feeTokenContractsByChain[chain.Selector] = feeTokenContracts } - return feeTokenContractsByChain, nil + return nil } // DeployFeeTokens deploys link and weth9. This is _usually_ for test environments only, diff --git a/deployment/ccip/deploy_test.go b/deployment/ccip/deploy_test.go index 40b4a2a9786..ecb17017193 100644 --- a/deployment/ccip/deploy_test.go +++ b/deployment/ccip/deploy_test.go @@ -22,28 +22,28 @@ func TestDeployCCIPContracts(t *testing.T) { Nodes: 4, }) // Deploy all the CCIP contracts. - ab := deployment.NewMemoryAddressBook() homeChainSel, feedChainSel := allocateCCIPChainSelectors(e.Chains) - _, _ = DeployTestContracts(t, lggr, ab, homeChainSel, feedChainSel, e.Chains) + _ = DeployTestContracts(t, lggr, e.ExistingAddresses, homeChainSel, feedChainSel, e.Chains) // Load the state after deploying the cap reg and feeds. - s, err := LoadOnchainState(e, ab) + s, err := LoadOnchainState(e) require.NoError(t, err) require.NotNil(t, s.Chains[homeChainSel].CapabilityRegistry) require.NotNil(t, s.Chains[homeChainSel].CCIPHome) require.NotNil(t, s.Chains[feedChainSel].USDFeeds) - err = DeployCCIPContracts(e, ab, DeployCCIPContractConfig{ - HomeChainSel: homeChainSel, - FeedChainSel: feedChainSel, - ChainsToDeploy: e.AllChainSelectors(), - TokenConfig: NewTokenConfig(), - ExistingAddressBook: ab, - MCMSConfig: NewTestMCMSConfig(t, e), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + newAddresses := deployment.NewMemoryAddressBook() + err = DeployCCIPContracts(e, newAddresses, DeployCCIPContractConfig{ + HomeChainSel: homeChainSel, + FeedChainSel: feedChainSel, + ChainsToDeploy: e.AllChainSelectors(), + TokenConfig: NewTokenConfig(), + MCMSConfig: NewTestMCMSConfig(t, e), + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) - state, err := LoadOnchainState(e, ab) + require.NoError(t, e.ExistingAddresses.Merge(newAddresses)) + state, err := LoadOnchainState(e) require.NoError(t, err) snap, err := state.View(e.AllChainSelectors()) require.NoError(t, err) diff --git a/deployment/ccip/state.go b/deployment/ccip/state.go index 22cc5cec9c1..528d21700cf 100644 --- a/deployment/ccip/state.go +++ b/deployment/ccip/state.go @@ -157,7 +157,7 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) { chainView.RMNProxy[c.RMNProxy.Address().Hex()] = rmnProxyView } if c.CapabilityRegistry != nil { - capRegView, err := common_v1_0.GenerateCapRegView(c.CapabilityRegistry) + capRegView, err := common_v1_0.GenerateCapabilityRegistryView(c.CapabilityRegistry) if err != nil { return chainView, err } @@ -201,12 +201,12 @@ func (s CCIPOnChainState) View(chains []uint64) (map[string]view.ChainView, erro return m, nil } -func LoadOnchainState(e deployment.Environment, ab deployment.AddressBook) (CCIPOnChainState, error) { +func LoadOnchainState(e deployment.Environment) (CCIPOnChainState, error) { state := CCIPOnChainState{ Chains: make(map[uint64]CCIPChainState), } for chainSelector, chain := range e.Chains { - addresses, err := ab.AddressesForChain(chainSelector) + addresses, err := e.ExistingAddresses.AddressesForChain(chainSelector) if err != nil { // Chain not found in address book, initialize empty if errors.Is(err, deployment.ErrChainNotFound) { diff --git a/deployment/ccip/test_helpers.go b/deployment/ccip/test_helpers.go index 84fd92bc626..3f9db87db15 100644 --- a/deployment/ccip/test_helpers.go +++ b/deployment/ccip/test_helpers.go @@ -3,6 +3,7 @@ package ccipdeployment import ( "context" "fmt" + mapset "github.com/deckarep/golang-set/v2" "math/big" "sort" "testing" @@ -61,12 +62,10 @@ func Context(tb testing.TB) context.Context { } type DeployedEnv struct { - Env deployment.Environment - Ab deployment.AddressBook - HomeChainSel uint64 - FeedChainSel uint64 - ReplayBlocks map[uint64]uint64 - FeeTokenContracts map[uint64]FeeTokenContracts + Env deployment.Environment + HomeChainSel uint64 + FeedChainSel uint64 + ReplayBlocks map[uint64]uint64 } func (e *DeployedEnv) SetupJobs(t *testing.T) { @@ -107,16 +106,16 @@ func DeployTestContracts(t *testing.T, homeChainSel, feedChainSel uint64, chains map[uint64]deployment.Chain, -) (map[uint64]FeeTokenContracts, deployment.CapabilityRegistryConfig) { +) deployment.CapabilityRegistryConfig { capReg, err := DeployCapReg(lggr, ab, chains[homeChainSel]) require.NoError(t, err) _, err = DeployFeeds(lggr, ab, chains[feedChainSel]) require.NoError(t, err) - feeTokenContracts, err := DeployFeeTokensToChains(lggr, ab, chains) + err = DeployFeeTokensToChains(lggr, ab, chains) require.NoError(t, err) evmChainID, err := chainsel.ChainIdFromSelector(homeChainSel) require.NoError(t, err) - return feeTokenContracts, deployment.CapabilityRegistryConfig{ + return deployment.CapabilityRegistryConfig{ EVMChainID: evmChainID, Contract: capReg.Address, } @@ -161,7 +160,7 @@ func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int, numNo require.NoError(t, err) ab := deployment.NewMemoryAddressBook() - feeTokenContracts, crConfig := DeployTestContracts(t, lggr, ab, homeChainSel, feedSel, chains) + crConfig := DeployTestContracts(t, lggr, ab, homeChainSel, feedSel, chains) nodes := memory.NewNodes(t, zapcore.InfoLevel, chains, numNodes, 1, crConfig) for _, node := range nodes { require.NoError(t, node.App.Start(ctx)) @@ -171,13 +170,12 @@ func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, numChains int, numNo } e := memory.NewMemoryEnvironmentFromChainsNodes(t, lggr, chains, nodes) + e.ExistingAddresses = ab return DeployedEnv{ - Ab: ab, - Env: e, - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ReplayBlocks: replayBlocks, - FeeTokenContracts: feeTokenContracts, + Env: e, + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ReplayBlocks: replayBlocks, } } @@ -277,7 +275,7 @@ const ( var ( MockLinkPrice = big.NewInt(5e18) - MockWethPrice = big.NewInt(9e18) + MockWethPrice = big.NewInt(9e8) // MockDescriptionToTokenSymbol maps a mock feed description to token descriptor MockDescriptionToTokenSymbol = map[string]TokenSymbol{ MockLinkAggregatorDescription: LinkSymbol, @@ -391,3 +389,31 @@ func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, sta return nil } + +func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.ChangesetOutput) { + + // TODO: Add support for jobspecs as well + + // sign and execute all proposals provided + if len(c.Proposals) != 0 { + state, err := LoadOnchainState(e) + require.NoError(t, err) + for _, prop := range c.Proposals { + chains := mapset.NewSet[uint64]() + for _, op := range prop.Transactions { + chains.Add(uint64(op.ChainIdentifier)) + } + + signed := SignProposal(t, e, &prop) + for _, sel := range chains.ToSlice() { + ExecuteProposal(t, e, signed, state, sel) + } + } + } + + // merge address books + if c.AddressBook != nil { + err := e.ExistingAddresses.Merge(c.AddressBook) + require.NoError(t, err) + } +} diff --git a/deployment/ccip/view/view.go b/deployment/ccip/view/view.go index 1882e040a65..9ef8583bdf6 100644 --- a/deployment/ccip/view/view.go +++ b/deployment/ccip/view/view.go @@ -20,12 +20,12 @@ type ChainView struct { TokenAdminRegistry map[string]v1_5.TokenAdminRegistryView `json:"tokenAdminRegistry,omitempty"` CommitStore map[string]v1_5.CommitStoreView `json:"commitStore,omitempty"` // v1.6 - FeeQuoter map[string]v1_6.FeeQuoterView `json:"feeQuoter,omitempty"` - NonceManager map[string]v1_6.NonceManagerView `json:"nonceManager,omitempty"` - RMN map[string]v1_6.RMNRemoteView `json:"rmn,omitempty"` - OnRamp map[string]v1_6.OnRampView `json:"onRamp,omitempty"` - OffRamp map[string]v1_6.OffRampView `json:"offRamp,omitempty"` - CapabilityRegistry map[string]common_v1_0.CapRegView `json:"capabilityRegistry,omitempty"` + FeeQuoter map[string]v1_6.FeeQuoterView `json:"feeQuoter,omitempty"` + NonceManager map[string]v1_6.NonceManagerView `json:"nonceManager,omitempty"` + RMN map[string]v1_6.RMNRemoteView `json:"rmn,omitempty"` + OnRamp map[string]v1_6.OnRampView `json:"onRamp,omitempty"` + OffRamp map[string]v1_6.OffRampView `json:"offRamp,omitempty"` + CapabilityRegistry map[string]common_v1_0.CapabilityRegistryView `json:"capabilityRegistry,omitempty"` } func NewChain() ChainView { @@ -43,7 +43,7 @@ func NewChain() ChainView { RMN: make(map[string]v1_6.RMNRemoteView), OnRamp: make(map[string]v1_6.OnRampView), OffRamp: make(map[string]v1_6.OffRampView), - CapabilityRegistry: make(map[string]common_v1_0.CapRegView), + CapabilityRegistry: make(map[string]common_v1_0.CapabilityRegistryView), } } @@ -53,5 +53,7 @@ type CCIPView struct { } func (v CCIPView) MarshalJSON() ([]byte, error) { - return json.Marshal(v) + // Alias to avoid recursive calls + type Alias CCIPView + return json.MarshalIndent(&struct{ Alias }{Alias: Alias(v)}, "", " ") } diff --git a/deployment/changeset.go b/deployment/changeset.go index 308ce60f7a9..687d772bf73 100644 --- a/deployment/changeset.go +++ b/deployment/changeset.go @@ -21,13 +21,15 @@ var ( type ChangeSet func(e Environment, config interface{}) (ChangesetOutput, error) // ChangesetOutput is the output of a Changeset function. +// Think of it like a state transition output. +// The address book here should contain only new addresses created in +// this changeset. type ChangesetOutput struct { - JobSpecs map[string][]string - Proposals []timelock.MCMSWithTimelockProposal - // The address book here should contain only new addresses created in this changeset. + JobSpecs map[string][]string + Proposals []timelock.MCMSWithTimelockProposal AddressBook AddressBook } // ViewState produces a product specific JSON representation of // the on and offchain state of the environment. -type ViewState func(e Environment, ab AddressBook) (json.Marshaler, error) +type ViewState func(e Environment) (json.Marshaler, error) diff --git a/deployment/common/view/v1_0/capreg.go b/deployment/common/view/v1_0/capreg.go index b509fad0a04..92d44af5983 100644 --- a/deployment/common/view/v1_0/capreg.go +++ b/deployment/common/view/v1_0/capreg.go @@ -1,45 +1,375 @@ package v1_0 import ( - "github.com/ethereum/go-ethereum/common" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "math/big" + "slices" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink/deployment/common/view/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) -// CapRegView denotes a view of the capabilities registry contract. -// Note that the contract itself is 1.0.0 versioned, but we're releasing it first -// as part of 1.6 for CCIP. -type CapRegView struct { +// CapabilityRegistryView is a high-fidelity view of the capabilities registry contract. +type CapabilityRegistryView struct { types.ContractMetaData Capabilities []CapabilityView `json:"capabilities,omitempty"` + Nodes []NodeView `json:"nodes,omitempty"` + Nops []NopView `json:"nops,omitempty"` + Dons []DonView `json:"dons,omitempty"` } -type CapabilityView struct { - LabelledName string `json:"labelledName"` - Version string `json:"version"` - ConfigContract common.Address `json:"configContract"` +// MarshalJSON marshals the CapabilityRegistryView to JSON. It includes the Capabilities, Nodes, Nops, and Dons +// and a denormalized summary of the Dons with their associated Nodes and Capabilities, which is useful for a high-level view +func (v CapabilityRegistryView) MarshalJSON() ([]byte, error) { + // Alias to avoid recursive calls + type Alias struct { + types.ContractMetaData + Capabilities []CapabilityView `json:"capabilities,omitempty"` + Nodes []NodeView `json:"nodes,omitempty"` + Nops []NopView `json:"nops,omitempty"` + Dons []DonView `json:"dons,omitempty"` + DonCapabilities []DonDenormalizedView `json:"don_capabilities_summary,omitempty"` + } + a := Alias{ + ContractMetaData: v.ContractMetaData, + Capabilities: v.Capabilities, + Nodes: v.Nodes, + Nops: v.Nops, + Dons: v.Dons, + } + dc, err := v.DonDenormalizedView() + if err != nil { + return nil, err + } + a.DonCapabilities = dc + return json.MarshalIndent(&a, "", " ") } -func GenerateCapRegView(capReg *capabilities_registry.CapabilitiesRegistry) (CapRegView, error) { +// GenerateCapabilityRegistryView generates a CapRegView from a CapabilitiesRegistry contract. +func GenerateCapabilityRegistryView(capReg *capabilities_registry.CapabilitiesRegistry) (CapabilityRegistryView, error) { tv, err := types.NewContractMetaData(capReg, capReg.Address()) if err != nil { - return CapRegView{}, err + return CapabilityRegistryView{}, err } caps, err := capReg.GetCapabilities(nil) if err != nil { - return CapRegView{}, err + return CapabilityRegistryView{}, err } var capViews []CapabilityView for _, capability := range caps { - capViews = append(capViews, CapabilityView{ - LabelledName: capability.LabelledName, - Version: capability.Version, - ConfigContract: capability.ConfigurationContract, - }) + capViews = append(capViews, NewCapabilityView(capability)) + } + donInfos, err := capReg.GetDONs(nil) + if err != nil { + return CapabilityRegistryView{}, err + } + var donViews []DonView + for _, donInfo := range donInfos { + donViews = append(donViews, NewDonView(donInfo)) + } + + nodeInfos, err := capReg.GetNodes(nil) + if err != nil { + return CapabilityRegistryView{}, err + } + var nodeViews []NodeView + for _, nodeInfo := range nodeInfos { + nodeViews = append(nodeViews, NewNodeView(nodeInfo)) } - return CapRegView{ + + nopInfos, err := capReg.GetNodeOperators(nil) + if err != nil { + return CapabilityRegistryView{}, err + } + var nopViews []NopView + for _, nopInfo := range nopInfos { + nopViews = append(nopViews, NewNopView(nopInfo)) + } + + return CapabilityRegistryView{ ContractMetaData: tv, Capabilities: capViews, + Dons: donViews, + Nodes: nodeViews, + Nops: nopViews, }, nil } + +// DonDenormalizedView is a view of a Don with its associated Nodes and Capabilities. +type DonDenormalizedView struct { + Don DonUniversalMetadata `json:"don"` + Nodes []NodeDenormalizedView `json:"nodes"` + Capabilities []CapabilityView `json:"capabilities"` +} + +// DonDenormalizedView returns a list of DonDenormalizedView, which are Dons with their associated +// Nodes and Capabilities. This is a useful form of the CapabilityRegistryView, but it is not definitive. +// The full CapRegView should be used for the most accurate information as it can contain +// Capabilities and Nodes the are not associated with any Don. +func (v CapabilityRegistryView) DonDenormalizedView() ([]DonDenormalizedView, error) { + var out []DonDenormalizedView + for _, don := range v.Dons { + var nodes []NodeDenormalizedView + for _, node := range v.Nodes { + if don.hasNode(node) { + ndv, err := v.nodeDenormalizedView(node) + if err != nil { + return nil, err + } + nodes = append(nodes, ndv) + } + } + var capabilities []CapabilityView + for _, cap := range v.Capabilities { + if don.hasCapability(cap) { + capabilities = append(capabilities, cap) + } + } + out = append(out, DonDenormalizedView{ + Don: don.DonUniversalMetadata, + Nodes: nodes, + Capabilities: capabilities, + }) + } + return out, nil +} + +// CapabilityView is a serialization-friendly view of a capability in the capabilities registry. +type CapabilityView struct { + ID string `json:"id"` // hex 32 bytes + LabelledName string `json:"labelled_name"` + Version string `json:"version"` + CapabilityType uint8 `json:"capability_type"` + ResponseType uint8 `json:"response_type"` + ConfigurationContract common.Address `json:"configuration_contract,omitempty"` + IsDeprecated bool `json:"is_deprecated,omitempty"` +} + +// NewCapabilityView creates a CapabilityView from a CapabilitiesRegistryCapabilityInfo. +func NewCapabilityView(capInfo capabilities_registry.CapabilitiesRegistryCapabilityInfo) CapabilityView { + return CapabilityView{ + ID: hex.EncodeToString(capInfo.HashedId[:]), + LabelledName: capInfo.LabelledName, + Version: capInfo.Version, + CapabilityType: capInfo.CapabilityType, + ResponseType: capInfo.ResponseType, + ConfigurationContract: capInfo.ConfigurationContract, + IsDeprecated: capInfo.IsDeprecated, + } +} + +// Validate checks that the CapabilityView is valid. +func (cv CapabilityView) Validate() error { + id, err := hex.DecodeString(cv.ID) + if err != nil { + return err + } + if len(id) != 32 { + return errors.New("capability id must be 32 bytes") + } + return nil +} + +// DonView is a serialization-friendly view of a Don in the capabilities registry. +type DonView struct { + DonUniversalMetadata + NodeP2PIds []p2pkey.PeerID `json:"node_p2p_ids,omitempty"` + CapabilityConfigurations []CapabilitiesConfiguration `json:"capability_configurations,omitempty"` +} + +type DonUniversalMetadata struct { + ID uint32 `json:"id"` + ConfigCount uint32 `json:"config_count"` + F uint8 `json:"f"` + IsPublic bool `json:"is_public,omitempty"` + AcceptsWorkflows bool `json:"accepts_workflows,omitempty"` +} + +// NewDonView creates a DonView from a CapabilitiesRegistryDONInfo. +func NewDonView(d capabilities_registry.CapabilitiesRegistryDONInfo) DonView { + return DonView{ + DonUniversalMetadata: DonUniversalMetadata{ + ID: d.Id, + ConfigCount: d.ConfigCount, + F: d.F, + IsPublic: d.IsPublic, + AcceptsWorkflows: d.AcceptsWorkflows, + }, + NodeP2PIds: p2pIds(d.NodeP2PIds), + CapabilityConfigurations: NewCapabilityConfigurations(d.CapabilityConfigurations), + } +} + +func (dv DonView) Validate() error { + for i, cfg := range dv.CapabilityConfigurations { + if err := cfg.Validate(); err != nil { + return fmt.Errorf("capability configuration at index %d invalid:%w ", i, err) + } + } + return nil +} + +// CapabilitiesConfiguration is a serialization-friendly view of a capability configuration in the capabilities registry. +type CapabilitiesConfiguration struct { + ID string `json:"id"` // hex 32 bytes + Config string `json:"config"` // hex +} + +// NewCapabilityConfigurations creates a list of CapabilitiesConfiguration from a list of CapabilitiesRegistryCapabilityConfiguration. +func NewCapabilityConfigurations(cfgs []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration) []CapabilitiesConfiguration { + var out []CapabilitiesConfiguration + for _, cfg := range cfgs { + out = append(out, CapabilitiesConfiguration{ + ID: hex.EncodeToString(cfg.CapabilityId[:]), + Config: hex.EncodeToString(cfg.Config), + }) + } + return out +} + +func (cc CapabilitiesConfiguration) Validate() error { + id, err := hex.DecodeString(cc.ID) + if err != nil { + return errors.New("capability id must be hex encoded") + } + if len(id) != 32 { + return errors.New("capability id must be 32 bytes") + } + _, err = hex.DecodeString(cc.Config) + if err != nil { + return errors.New("config must be hex encoded") + } + return nil +} + +// NodeView is a serialization-friendly view of a node in the capabilities registry. +type NodeView struct { + NodeUniversalMetadata + NodeOperatorID uint32 `json:"node_operator_id"` + CapabilityIDs []string `json:"capability_ids,omitempty"` // hex 32 bytes + DONIDs []*big.Int `json:",don_ids, omitempty"` +} + +// NodeUniversalMetadata is a serialization-friendly view of the universal metadata of a node in the capabilities registry. +type NodeUniversalMetadata struct { + ConfigCount uint32 `json:"config_count"` + WorkflowDONID uint32 `json:"workflow_don_id"` + Signer string `json:"signer"` // hex 32 bytes + P2pId p2pkey.PeerID `json:"p2p_id"` + EncryptionPublicKey string `json:"encryption_public_key"` // hex 32 bytes +} + +// NewNodeView creates a NodeView from a CapabilitiesRegistryNodeInfoProviderNodeInfo. +func NewNodeView(n capabilities_registry.INodeInfoProviderNodeInfo) NodeView { + return NodeView{ + NodeUniversalMetadata: NodeUniversalMetadata{ + ConfigCount: n.ConfigCount, + WorkflowDONID: n.WorkflowDONId, + Signer: hex.EncodeToString(n.Signer[:]), + P2pId: p2pkey.PeerID(n.P2pId), + EncryptionPublicKey: hex.EncodeToString(n.EncryptionPublicKey[:]), + }, + NodeOperatorID: n.NodeOperatorId, + CapabilityIDs: hexIds(n.HashedCapabilityIds), + DONIDs: n.CapabilitiesDONIds, + } +} + +func (nv NodeView) Validate() error { + s, err := hex.DecodeString(nv.Signer) + if err != nil { + return errors.New("signer must be hex encoded") + } + if len(s) != 32 { + return errors.New("signer must be 32 bytes") + } + + e, err := hex.DecodeString(nv.EncryptionPublicKey) + if err != nil { + return errors.New("encryption public key must be hex encoded") + } + if len(e) != 32 { + return errors.New("encryption public key must be 32 bytes") + } + + for _, id := range nv.CapabilityIDs { + cid, err := hex.DecodeString(id) + if err != nil { + return errors.New("hashed capability id must be hex encoded") + } + if len(cid) != 32 { + return errors.New("hashed capability id must be 32 bytes") + } + } + return nil +} + +// NodeDenormalizedView is a serialization-friendly view of a node in the capabilities registry with its associated NOP. +type NodeDenormalizedView struct { + NodeUniversalMetadata + Nop NopView `json:"nop"` +} + +type NopView struct { + Admin common.Address `json:"admin"` + Name string `json:"name"` +} + +func NewNopView(nop capabilities_registry.CapabilitiesRegistryNodeOperator) NopView { + return NopView{ + Admin: nop.Admin, + Name: nop.Name, + } +} + +func (v CapabilityRegistryView) nodeDenormalizedView(n NodeView) (NodeDenormalizedView, error) { + nop, err := nodeNop(n, v.Nops) + if err != nil { + return NodeDenormalizedView{}, err + } + return NodeDenormalizedView{ + NodeUniversalMetadata: n.NodeUniversalMetadata, + Nop: nop, + }, nil +} + +func nodeNop(n NodeView, nops []NopView) (NopView, error) { + for i, nop := range nops { + // nops are 1-indexed. there is no natural key to match on, so we use the index. + idx := i + 1 + if n.NodeOperatorID == uint32(idx) { + return nop, nil + } + } + return NopView{}, fmt.Errorf("could not find nop for node %d", n.NodeOperatorID) +} + +func p2pIds(rawIds [][32]byte) []p2pkey.PeerID { + var out []p2pkey.PeerID + for _, id := range rawIds { + out = append(out, p2pkey.PeerID(id)) + } + return out +} + +func hexIds(ids [][32]byte) []string { + var out []string + for _, id := range ids { + out = append(out, hex.EncodeToString(id[:])) + } + return out +} + +func (v DonView) hasNode(node NodeView) bool { + donId := big.NewInt(int64(v.ID)) + return slices.ContainsFunc(node.DONIDs, func(elem *big.Int) bool { return elem.Cmp(donId) == 0 }) +} + +func (v DonView) hasCapability(candidate CapabilityView) bool { + return slices.ContainsFunc(v.CapabilityConfigurations, func(elem CapabilitiesConfiguration) bool { return elem.ID == candidate.ID }) +} diff --git a/deployment/common/view/v1_0/capreg_test.go b/deployment/common/view/v1_0/capreg_test.go new file mode 100644 index 00000000000..8ba7b880364 --- /dev/null +++ b/deployment/common/view/v1_0/capreg_test.go @@ -0,0 +1,267 @@ +package v1_0 + +import ( + "math/big" + "testing" + + "github.com/smartcontractkit/chainlink/deployment/common/view/types" + cr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/stretchr/testify/assert" +) + +func TestCapRegView_Denormalize(t *testing.T) { + type fields struct { + ContractMetaData types.ContractMetaData + Capabilities []CapabilityView + Nodes []NodeView + Dons []DonView + Nops []NopView + } + tests := []struct { + name string + fields fields + want []DonDenormalizedView + wantErr bool + }{ + { + name: "empty", + fields: fields{}, + want: nil, + }, + { + name: "one don", + fields: fields{ + Dons: []DonView{ + NewDonView(cr.CapabilitiesRegistryDONInfo{ + Id: 1, + CapabilityConfigurations: []cr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: [32]byte{0: 1}, + }, + }, + }), + }, + Nodes: []NodeView{ + NewNodeView(cr.INodeInfoProviderNodeInfo{ + CapabilitiesDONIds: []*big.Int{big.NewInt(1)}, + NodeOperatorId: 1, // 1-based index + }), + }, + Nops: []NopView{ + {Name: "first nop"}, + }, + Capabilities: []CapabilityView{ + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{0: 1}, + LabelledName: "cap1", + Version: "1.0.0", + }), + + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{0: 2}, + LabelledName: "cap2", + Version: "1.0.0", + }), + }, + }, + want: []DonDenormalizedView{ + { + Don: NewDonView(cr.CapabilitiesRegistryDONInfo{ + Id: 1, + CapabilityConfigurations: []cr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: [32]byte{0: 1}, + }, + }, + }).DonUniversalMetadata, + Nodes: []NodeDenormalizedView{ + { + NodeUniversalMetadata: NewNodeView(cr.INodeInfoProviderNodeInfo{ + CapabilitiesDONIds: []*big.Int{big.NewInt(1)}, + NodeOperatorId: 1, // 1-based index + }).NodeUniversalMetadata, + Nop: NopView{Name: "first nop"}, + }, + }, + Capabilities: []CapabilityView{ + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{0: 1}, + LabelledName: "cap1", + Version: "1.0.0", + }), + }, + }, + }, + }, + + { + name: "two dons, multiple capabilities", + fields: fields{ + Dons: []DonView{ + // don1 + NewDonView(cr.CapabilitiesRegistryDONInfo{ + Id: 1, + CapabilityConfigurations: []cr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: [32]byte{0: 1}, + }, + { + CapabilityId: [32]byte{1: 1}, + }, + }, + }), + + // don2 + NewDonView(cr.CapabilitiesRegistryDONInfo{ + Id: 2, + CapabilityConfigurations: []cr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: [32]byte{2: 2}, + }, + }, + }), + }, + Nodes: []NodeView{ + // nodes for don1 + NewNodeView(cr.INodeInfoProviderNodeInfo{ + P2pId: [32]byte{31: 1}, + CapabilitiesDONIds: []*big.Int{big.NewInt(1)}, // matches don ID 1 + NodeOperatorId: 1, // 1-based index + }), + + NewNodeView(cr.INodeInfoProviderNodeInfo{ + P2pId: [32]byte{31: 11}, + CapabilitiesDONIds: []*big.Int{big.NewInt(1)}, // matches don ID 1 + NodeOperatorId: 3, // 1-based index + }), + + // nodes for don2 + NewNodeView(cr.INodeInfoProviderNodeInfo{ + P2pId: [32]byte{31: 22}, + CapabilitiesDONIds: []*big.Int{big.NewInt(2)}, // matches don ID 2 + NodeOperatorId: 2, // 1-based index + }), + }, + Nops: []NopView{ + {Name: "first nop"}, + {Name: "second nop"}, + {Name: "third nop"}, + }, + Capabilities: []CapabilityView{ + //capabilities for don1 + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{0: 1}, + LabelledName: "cap1", + Version: "1.0.0", + }), + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{1: 1}, // matches don ID 1, capabitility ID 2 + LabelledName: "cap2", + Version: "1.0.0", + }), + + //capabilities for don2 + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{2: 2}, // matches don ID 2, capabitility ID 1 + LabelledName: "other cap", + Version: "1.0.0", + }), + }, + }, + want: []DonDenormalizedView{ + { + Don: NewDonView(cr.CapabilitiesRegistryDONInfo{ + Id: 1, + CapabilityConfigurations: []cr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: [32]byte{0: 1}, + }, + { + CapabilityId: [32]byte{1: 1}, + }, + }, + }).DonUniversalMetadata, + Nodes: []NodeDenormalizedView{ + { + NodeUniversalMetadata: NewNodeView(cr.INodeInfoProviderNodeInfo{ + P2pId: [32]byte{31: 1}, + CapabilitiesDONIds: []*big.Int{big.NewInt(1)}, // matches don ID 1 + }).NodeUniversalMetadata, + Nop: NopView{Name: "first nop"}, + }, + { + NodeUniversalMetadata: NewNodeView(cr.INodeInfoProviderNodeInfo{ + P2pId: [32]byte{31: 11}, + CapabilitiesDONIds: []*big.Int{big.NewInt(1)}, // matches don ID 1 + }).NodeUniversalMetadata, + Nop: NopView{Name: "third nop"}, + }, + }, + Capabilities: []CapabilityView{ + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{0: 1}, + LabelledName: "cap1", + Version: "1.0.0", + }), + + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{1: 1}, + LabelledName: "cap2", + Version: "1.0.0", + }), + }, + }, + { + Don: NewDonView(cr.CapabilitiesRegistryDONInfo{ + Id: 2, + CapabilityConfigurations: []cr.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: [32]byte{2: 2}, + }, + }, + }).DonUniversalMetadata, + Nodes: []NodeDenormalizedView{ + { + NodeUniversalMetadata: NewNodeView(cr.INodeInfoProviderNodeInfo{ + P2pId: [32]byte{31: 22}, + CapabilitiesDONIds: []*big.Int{big.NewInt(2)}, // matches don ID 2 + }).NodeUniversalMetadata, + Nop: NopView{Name: "second nop"}, + }, + }, + Capabilities: []CapabilityView{ + NewCapabilityView(cr.CapabilitiesRegistryCapabilityInfo{ + HashedId: [32]byte{2: 2}, // matches don ID 2, capabitility ID 1 + LabelledName: "other cap", + Version: "1.0.0", + }), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v := CapabilityRegistryView{ + ContractMetaData: tt.fields.ContractMetaData, + Capabilities: tt.fields.Capabilities, + Nodes: tt.fields.Nodes, + Dons: tt.fields.Dons, + Nops: tt.fields.Nops, + } + got, err := v.DonDenormalizedView() + if (err != nil) != tt.wantErr { + t.Errorf("CapRegView.Denormalize() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + assert.Equal(t, tt.want[i].Don, got[i].Don) + for j := range got[i].Nodes { + assert.Equal(t, tt.want[i].Nodes[j].NodeUniversalMetadata, got[i].Nodes[j].NodeUniversalMetadata, "NodeUniversalMetadata mismatch at index %d for don %d", j, i) + assert.Equal(t, tt.want[i].Nodes[j].Nop, got[i].Nodes[j].Nop, "Nop mismatch at index %d for don %d", j, i) + } + assert.Equal(t, tt.want[i].Capabilities, got[i].Capabilities) + } + }) + } +} diff --git a/deployment/environment.go b/deployment/environment.go index 99c15233dfa..a975042dee2 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -27,22 +27,26 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) +// OnchainClient is an EVM chain client. +// For EVM specifically we can use existing geth interface +// to abstract chain clients. type OnchainClient interface { - // For EVM specifically we can use existing geth interface - // to abstract chain clients. bind.ContractBackend bind.DeployBackend BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) } +// OffchainClient interacts with the job-distributor +// which is a family agnostic interface for performing +// DON operations. type OffchainClient interface { - // The job distributor grpc interface can be used to abstract offchain read/writes jobv1.JobServiceClient nodev1.NodeServiceClient csav1.CSAServiceClient } +// Chain represents an EVM chain. type Chain struct { // Selectors used as canonical chain identifier. Selector uint64 @@ -52,12 +56,44 @@ type Chain struct { Confirm func(tx *types.Transaction) (uint64, error) } +// Environment represents an instance of a deployed product +// including on and offchain components. It is intended to be +// cross-family to enable a coherent view of a product deployed +// to all its chains. +// TODO: Add SolChains, AptosChain etc. +// using Go bindings/libraries from their respective +// repositories i.e. chainlink-solana, chainlink-cosmos +// You can think of ExistingAddresses as a set of +// family agnostic "onchain pointers" meant to be used in conjunction +// with chain fields to read/write relevant chain state. Similarly, +// you can think of NodeIDs as "offchain pointers" to be used in +// conjunction with the Offchain client to read/write relevant +// offchain state (i.e. state in the DON(s)). type Environment struct { - Name string - Chains map[uint64]Chain - Offchain OffchainClient - NodeIDs []string - Logger logger.Logger + Name string + Logger logger.Logger + ExistingAddresses AddressBook + Chains map[uint64]Chain + NodeIDs []string + Offchain OffchainClient +} + +func NewEnvironment( + name string, + logger logger.Logger, + existingAddrs AddressBook, + chains map[uint64]Chain, + nodeIDs []string, + offchain OffchainClient, +) *Environment { + return &Environment{ + Name: name, + Logger: logger, + ExistingAddresses: existingAddrs, + Chains: chains, + NodeIDs: nodeIDs, + Offchain: offchain, + } } func (e Environment) AllChainSelectors() []uint64 { diff --git a/deployment/environment/clo/env.go b/deployment/environment/clo/env.go index 201917d61aa..d1683ad4e1e 100644 --- a/deployment/environment/clo/env.go +++ b/deployment/environment/clo/env.go @@ -30,13 +30,14 @@ func NewDonEnv(t *testing.T, cfg DonEnvConfig) *deployment.Environment { } } } - out := deployment.Environment{ - Name: cfg.DonName, - Offchain: NewJobClient(cfg.Logger, cfg.Nops), - NodeIDs: make([]string, 0), - Chains: cfg.Chains, - Logger: cfg.Logger, - } + out := deployment.NewEnvironment( + cfg.DonName, + cfg.Logger, + deployment.NewMemoryAddressBook(), + cfg.Chains, + make([]string, 0), + NewJobClient(cfg.Logger, cfg.Nops), + ) // assume that all the nodes in the provided input nops are part of the don for _, nop := range cfg.Nops { for _, node := range nop.Nodes { @@ -44,7 +45,7 @@ func NewDonEnv(t *testing.T, cfg DonEnvConfig) *deployment.Environment { } } - return &out + return out } func NewDonEnvWithMemoryChains(t *testing.T, cfg DonEnvConfig, ignore func(*models.NodeChainConfig) bool) *deployment.Environment { @@ -87,18 +88,18 @@ type MultiDonEnvironment struct { } func (mde MultiDonEnvironment) Flatten(name string) *deployment.Environment { - return &deployment.Environment{ - Name: name, - Chains: mde.Chains, - Logger: mde.Logger, - - // TODO: KS-460 integrate with the clo offchain client impl - // may need to extend the Environment abstraction use maps rather than slices for Nodes - // somehow we need to capture the fact that each nodes belong to nodesets which have different capabilities - // purposely nil to catch misuse until we do that work - Offchain: nil, - NodeIDs: nil, - } + // TODO: KS-460 integrate with the clo offchain client impl + // may need to extend the Environment abstraction use maps rather than slices for Nodes + // somehow we need to capture the fact that each nodes belong to nodesets which have different capabilities + // purposely nil to catch misuse until we do that work + return deployment.NewEnvironment( + name, + mde.Logger, + deployment.NewMemoryAddressBook(), + mde.Chains, + nil, + nil, + ) } func newMultiDonEnvironment(logger logger.Logger, donToEnv map[string]*deployment.Environment) *MultiDonEnvironment { diff --git a/deployment/environment/devenv/chain.go b/deployment/environment/devenv/chain.go index 2a2b58e4100..cdbaf35e860 100644 --- a/deployment/environment/devenv/chain.go +++ b/deployment/environment/devenv/chain.go @@ -74,7 +74,7 @@ func NewChains(logger logger.Logger, configs []ChainConfig) (map[uint64]deployme } blockNumber = receipt.BlockNumber.Uint64() if receipt.Status == 0 { - errReason, err := deployment.GetErrorReasonFromTx(ec, chainCfg.DeployerKey.From, *tx, receipt) + errReason, err := deployment.GetErrorReasonFromTx(ec, chainCfg.DeployerKey.From, tx, receipt) if err == nil && errReason != "" { return blockNumber, fmt.Errorf("tx %s reverted,error reason: %s", tx.Hash().Hex(), errReason) } diff --git a/deployment/environment/devenv/environment.go b/deployment/environment/devenv/environment.go index fb54bac5b59..af39e59e3bf 100644 --- a/deployment/environment/devenv/environment.go +++ b/deployment/environment/devenv/environment.go @@ -44,11 +44,12 @@ func NewEnvironment(ctx context.Context, lggr logger.Logger, config EnvironmentC } nodeIDs := jd.don.NodeIds() - return &deployment.Environment{ - Name: DevEnv, - Offchain: offChain, - NodeIDs: nodeIDs, - Chains: chains, - Logger: lggr, - }, jd.don, nil + return deployment.NewEnvironment( + DevEnv, + lggr, + deployment.NewMemoryAddressBook(), + chains, + nodeIDs, + offChain, + ), jd.don, nil } diff --git a/deployment/environment/memory/environment.go b/deployment/environment/memory/environment.go index 75e74534184..22733571038 100644 --- a/deployment/environment/memory/environment.go +++ b/deployment/environment/memory/environment.go @@ -63,7 +63,7 @@ func generateMemoryChain(t *testing.T, inputs map[uint64]EVMChain) map[uint64]de continue } if receipt.Status == 0 { - errReason, err := deployment.GetErrorReasonFromTx(chain.Backend, chain.DeployerKey.From, *tx, receipt) + errReason, err := deployment.GetErrorReasonFromTx(chain.Backend, chain.DeployerKey.From, tx, receipt) if err == nil && errReason != "" { return 0, fmt.Errorf("tx %s reverted,error reason: %s", tx.Hash().Hex(), errReason) } @@ -116,14 +116,14 @@ func NewMemoryEnvironmentFromChainsNodes(t *testing.T, for id := range nodes { nodeIDs = append(nodeIDs, id) } - return deployment.Environment{ - Name: Memory, - Offchain: NewMemoryJobClient(nodes), - // Note these have the p2p_ prefix. - NodeIDs: nodeIDs, - Chains: chains, - Logger: lggr, - } + return *deployment.NewEnvironment( + Memory, + lggr, + deployment.NewMemoryAddressBook(), + chains, + nodeIDs, // Note these have the p2p_ prefix. + NewMemoryJobClient(nodes), + ) } // To be used by tests and any kind of deployment logic. @@ -134,11 +134,12 @@ func NewMemoryEnvironment(t *testing.T, lggr logger.Logger, logLevel zapcore.Lev for id := range nodes { nodeIDs = append(nodeIDs, id) } - return deployment.Environment{ - Name: Memory, - Offchain: NewMemoryJobClient(nodes), - NodeIDs: nodeIDs, - Chains: chains, - Logger: lggr, - } + return *deployment.NewEnvironment( + Memory, + lggr, + deployment.NewMemoryAddressBook(), + chains, + nodeIDs, + NewMemoryJobClient(nodes), + ) } diff --git a/deployment/environment/memory/job_client.go b/deployment/environment/memory/job_client.go index 0bd4035c78d..d572f5f92f5 100644 --- a/deployment/environment/memory/job_client.go +++ b/deployment/environment/memory/job_client.go @@ -55,8 +55,18 @@ func (j JobClient) ListKeypairs(ctx context.Context, in *csav1.ListKeypairsReque } func (j JobClient) GetNode(ctx context.Context, in *nodev1.GetNodeRequest, opts ...grpc.CallOption) (*nodev1.GetNodeResponse, error) { - //TODO CCIP-3108 implement me - panic("implement me") + n, ok := j.Nodes[in.Id] + if !ok { + return nil, errors.New("node not found") + } + return &nodev1.GetNodeResponse{ + Node: &nodev1.Node{ + Id: in.Id, + PublicKey: n.Keys.OCRKeyBundle.ID(), // is this the correct val? + IsEnabled: true, + IsConnected: true, + }, + }, nil } func (j JobClient) ListNodes(ctx context.Context, in *nodev1.ListNodesRequest, opts ...grpc.CallOption) (*nodev1.ListNodesResponse, error) { diff --git a/deployment/go.mod b/deployment/go.mod index 14630351641..4f5d3e95624 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -23,7 +23,7 @@ require ( github.com/sethvargo/go-retry v0.2.4 github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.27 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7 github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.13 diff --git a/deployment/go.sum b/deployment/go.sum index 5c4f0df9cad..0607d6161fd 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1382,8 +1382,8 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 h1:+VR9yKhbz+iCtWnCabaCDP18lIqGnk7YvGQIbJYgDrs= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 h1:GWjim4uGGFbye4XbJP0cPAbARhc8u3cAJU8jLYy0mXM= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7 h1:AGi0kAtMRW1zl1h7sGw+3CKO4Nlev6iA08YfEcgJCGs= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= diff --git a/deployment/helpers.go b/deployment/helpers.go index 226de9a7024..420e9e03f06 100644 --- a/deployment/helpers.go +++ b/deployment/helpers.go @@ -44,7 +44,7 @@ func SimTransactOpts() *bind.TransactOpts { }, From: common.HexToAddress("0x0"), NoSend: true, GasLimit: 1_000_000} } -func GetErrorReasonFromTx(client bind.ContractBackend, from common.Address, tx types.Transaction, receipt *types.Receipt) (string, error) { +func GetErrorReasonFromTx(client bind.ContractBackend, from common.Address, tx *types.Transaction, receipt *types.Receipt) (string, error) { call := ethereum.CallMsg{ From: from, To: tx.To(), diff --git a/deployment/keystone/capability_management.go b/deployment/keystone/capability_management.go index 19b6d9e9398..4e15ea897ab 100644 --- a/deployment/keystone/capability_management.go +++ b/deployment/keystone/capability_management.go @@ -62,3 +62,9 @@ func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, cha } return nil } + +// CapabilityID returns a unique id for the capability +// TODO: mv to chainlink-common? ref https://github.com/smartcontractkit/chainlink/blob/4fb06b4525f03c169c121a68defa9b13677f5f20/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol#L170 +func CapabilityID(c kcr.CapabilitiesRegistryCapability) string { + return fmt.Sprintf("%s@%s", c.LabelledName, c.Version) +} diff --git a/deployment/keystone/changeset/append_node_capbilities.go b/deployment/keystone/changeset/append_node_capbilities.go index 14b69307eae..20988825110 100644 --- a/deployment/keystone/changeset/append_node_capbilities.go +++ b/deployment/keystone/changeset/append_node_capbilities.go @@ -3,20 +3,23 @@ package changeset import ( "fmt" - "github.com/smartcontractkit/chainlink-common/pkg/logger" + chainsel "github.com/smartcontractkit/chain-selectors" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/deployment" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" ) +var _ deployment.ChangeSet = AppendNodeCapabilities + type AppendNodeCapabilitiesRequest struct { - Chain deployment.Chain - Registry *kcr.CapabilitiesRegistry + AddressBook deployment.AddressBook + RegistryChainSel uint64 P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *AppendNodeCapabilitiesRequest) Validate() error { @@ -26,12 +29,18 @@ func (req *AppendNodeCapabilitiesRequest) Validate() error { if len(req.NopToNodes) == 0 { return fmt.Errorf("nopToNodes is empty") } - if req.Registry == nil { + if req.AddressBook == nil { return fmt.Errorf("registry is nil") } + _, exists := chainsel.ChainBySelector(req.RegistryChainSel) + if !exists { + return fmt.Errorf("registry chain selector %d does not exist", req.RegistryChainSel) + } + return nil } +/* // AppendNodeCapabilibity adds any new capabilities to the registry, merges the new capabilities with the existing capabilities // of the node, and updates the nodes in the registry host the union of the new and existing capabilities. func AppendNodeCapabilities(lggr logger.Logger, req *AppendNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) { @@ -41,40 +50,51 @@ func AppendNodeCapabilities(lggr logger.Logger, req *AppendNodeCapabilitiesReque } return deployment.ChangesetOutput{}, nil } +*/ -func appendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesRequest) (*kslib.UpdateNodesResponse, error) { - if err := req.Validate(); err != nil { - return nil, fmt.Errorf("failed to validate request: %w", err) +// AppendNodeCapabilibity adds any new capabilities to the registry, merges the new capabilities with the existing capabilities +// of the node, and updates the nodes in the registry host the union of the new and existing capabilities. +func AppendNodeCapabilities(env deployment.Environment, config any) (deployment.ChangesetOutput, error) { + req, ok := config.(*AppendNodeCapabilitiesRequest) + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid config type") } - // collect all the capabilities and add them to the registry - var capabilities []kcr.CapabilitiesRegistryCapability - for _, cap := range req.P2pToCapabilities { - capabilities = append(capabilities, cap...) + + cfg, err := req.convert(env) + if err != nil { + return deployment.ChangesetOutput{}, err } - err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities) + _, err = internal.AppendNodeCapabilitiesImpl(env.Logger, cfg) if err != nil { - return nil, fmt.Errorf("failed to add capabilities: %w", err) + return deployment.ChangesetOutput{}, err } + return deployment.ChangesetOutput{}, nil +} - // for each node, merge the new capabilities with the existing ones and update the node - capsByPeer := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) - for p2pID, caps := range req.P2pToCapabilities { - caps, err := kslib.AppendCapabilities(lggr, req.Registry, req.Chain, []p2pkey.PeerID{p2pID}, caps) - if err != nil { - return nil, fmt.Errorf("failed to append capabilities for p2p %s: %w", p2pID, err) - } - capsByPeer[p2pID] = caps[p2pID] +func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*internal.AppendNodeCapabilitiesRequest, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("failed to validate UpdateNodeCapabilitiesRequest: %w", err) } - - updateNodesReq := &kslib.UpdateNodesRequest{ - Chain: req.Chain, - Registry: req.Registry, - P2pToCapabilities: capsByPeer, - NopToNodes: req.NopToNodes, + registryChain, ok := e.Chains[req.RegistryChainSel] + if !ok { + return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - resp, err := kslib.UpdateNodes(lggr, updateNodesReq) + contracts, err := kslib.GetContractSets(&kslib.GetContractSetsRequest{ + Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain}, + AddressBook: req.AddressBook, + }) if err != nil { - return nil, fmt.Errorf("failed to update nodes: %w", err) + return nil, fmt.Errorf("failed to get contract sets: %w", err) + } + registry := contracts.ContractSets[req.RegistryChainSel].CapabilitiesRegistry + if registry == nil { + return nil, fmt.Errorf("capabilities registry not found for chain %d", req.RegistryChainSel) } - return resp, nil + + return &internal.AppendNodeCapabilitiesRequest{ + Chain: registryChain, + Registry: registry, + P2pToCapabilities: req.P2pToCapabilities, + NopToNodes: req.NopToNodes, + }, nil } diff --git a/deployment/keystone/changeset/configure_contracts.go b/deployment/keystone/changeset/configure_contracts.go index 885b87189cd..d5bcb32243b 100644 --- a/deployment/keystone/changeset/configure_contracts.go +++ b/deployment/keystone/changeset/configure_contracts.go @@ -14,7 +14,7 @@ func ConfigureInitialContracts(lggr logger.Logger, req *kslib.ConfigureContracts return deployment.ChangesetOutput{}, fmt.Errorf("failed to validate request: %w", err) } - regAddrs, err := req.AddressBook.AddressesForChain(req.RegistryChainSel) + regAddrs, err := req.Env.ExistingAddresses.AddressesForChain(req.RegistryChainSel) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("no addresses found for chain %d: %w", req.RegistryChainSel, err) } @@ -38,7 +38,7 @@ func ConfigureInitialContracts(lggr logger.Logger, req *kslib.ConfigureContracts // forwarder on all chains foundForwarder = false for _, c := range req.Env.Chains { - addrs, err2 := req.AddressBook.AddressesForChain(c.Selector) + addrs, err2 := req.Env.ExistingAddresses.AddressesForChain(c.Selector) if err2 != nil { return deployment.ChangesetOutput{}, fmt.Errorf("no addresses found for chain %d: %w", c.Selector, err2) } diff --git a/deployment/keystone/changeset/deploy_forwarder.go b/deployment/keystone/changeset/deploy_forwarder.go index 4646412db83..d6adbee0252 100644 --- a/deployment/keystone/changeset/deploy_forwarder.go +++ b/deployment/keystone/changeset/deploy_forwarder.go @@ -9,24 +9,19 @@ import ( var _ deployment.ChangeSet = DeployForwarder -type DeployRegistryConfig struct { - RegistryChainSelector uint64 - ExistingAddressBook deployment.AddressBook -} - func DeployForwarder(env deployment.Environment, config interface{}) (deployment.ChangesetOutput, error) { - c, ok := config.(DeployRegistryConfig) + registryChainSel, ok := config.(uint64) if !ok { return deployment.ChangesetOutput{}, deployment.ErrInvalidConfig } lggr := env.Logger // expect OCR3 to be deployed & capabilities registry - regAddrs, err := c.ExistingAddressBook.AddressesForChain(c.RegistryChainSelector) + regAddrs, err := env.ExistingAddresses.AddressesForChain(registryChainSel) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("no addresses found for chain %d: %w", c.RegistryChainSelector, err) + return deployment.ChangesetOutput{}, fmt.Errorf("no addresses found for chain %d: %w", registryChainSel, err) } if len(regAddrs) != 2 { - return deployment.ChangesetOutput{}, fmt.Errorf("expected 2 addresses for chain %d, got %d", c.RegistryChainSelector, len(regAddrs)) + return deployment.ChangesetOutput{}, fmt.Errorf("expected 2 addresses for chain %d, got %d", registryChainSel, len(regAddrs)) } ab := deployment.NewMemoryAddressBook() for _, chain := range env.Chains { diff --git a/deployment/keystone/changeset/deploy_forwarder_test.go b/deployment/keystone/changeset/deploy_forwarder_test.go index 62e946b8a7b..8d73134fc1d 100644 --- a/deployment/keystone/changeset/deploy_forwarder_test.go +++ b/deployment/keystone/changeset/deploy_forwarder_test.go @@ -35,12 +35,9 @@ func TestDeployForwarder(t *testing.T) { m[registrySel] = map[string]deployment.TypeAndVersion{ "0x0000000000000000000000000000000000000002": ocrTV, } - ab := deployment.NewMemoryAddressBookFromMap(m) + env.ExistingAddresses = deployment.NewMemoryAddressBookFromMap(m) // capabilities registry and ocr3 must be deployed on registry chain - _, err := changeset.DeployForwarder(env, changeset.DeployRegistryConfig{ - RegistryChainSelector: registrySel, - ExistingAddressBook: ab, - }) + _, err := changeset.DeployForwarder(env, registrySel) require.Error(t, err) }) @@ -49,12 +46,9 @@ func TestDeployForwarder(t *testing.T) { m[registrySel] = map[string]deployment.TypeAndVersion{ "0x0000000000000000000000000000000000000001": crTV, } - ab := deployment.NewMemoryAddressBookFromMap(m) + env.ExistingAddresses = deployment.NewMemoryAddressBookFromMap(m) // capabilities registry and ocr3 must be deployed on registry chain - _, err := changeset.DeployForwarder(env, changeset.DeployRegistryConfig{ - RegistryChainSelector: registrySel, - ExistingAddressBook: ab, - }) + _, err := changeset.DeployForwarder(env, registrySel) require.Error(t, err) }) @@ -68,10 +62,8 @@ func TestDeployForwarder(t *testing.T) { err = ab.Save(registrySel, "0x0000000000000000000000000000000000000002", ocrTV) require.NoError(t, err) // deploy forwarder - resp, err := changeset.DeployForwarder(env, changeset.DeployRegistryConfig{ - RegistryChainSelector: registrySel, - ExistingAddressBook: ab, - }) + env.ExistingAddresses = ab + resp, err := changeset.DeployForwarder(env, registrySel) require.NoError(t, err) require.NotNil(t, resp) // registry, ocr3, forwarder should be deployed on registry chain diff --git a/deployment/keystone/changeset/helpers_test.go b/deployment/keystone/changeset/helpers_test.go deleted file mode 100644 index c15b59fc400..00000000000 --- a/deployment/keystone/changeset/helpers_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package changeset - -// AppendNodeCapabilitiesImpl exported so we can test the onchain result of the AppendNodeCapability Changeset function -var AppendNodeCapabilitiesImpl = appendNodeCapabilitiesImpl - -// UpdateNodeCapabilitiesImpl exported so we can test the onchain result of UpdateNodeCapability Changeset function -var UpdateNodeCapabilitiesImpl = updateNodeCapabilitiesImpl diff --git a/deployment/keystone/changeset/internal/append_node_capabilities.go b/deployment/keystone/changeset/internal/append_node_capabilities.go new file mode 100644 index 00000000000..f917039e8e0 --- /dev/null +++ b/deployment/keystone/changeset/internal/append_node_capabilities.go @@ -0,0 +1,69 @@ +package internal + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +type AppendNodeCapabilitiesRequest struct { + Chain deployment.Chain + Registry *kcr.CapabilitiesRegistry + + P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc +} + +func (req *AppendNodeCapabilitiesRequest) Validate() error { + if len(req.P2pToCapabilities) == 0 { + return fmt.Errorf("p2pToCapabilities is empty") + } + if len(req.NopToNodes) == 0 { + return fmt.Errorf("nopToNodes is empty") + } + if req.Registry == nil { + return fmt.Errorf("registry is nil") + } + return nil +} + +func AppendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesRequest) (*UpdateNodesResponse, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("failed to validate request: %w", err) + } + // collect all the capabilities and add them to the registry + var capabilities []kcr.CapabilitiesRegistryCapability + for _, cap := range req.P2pToCapabilities { + capabilities = append(capabilities, cap...) + } + err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities) + if err != nil { + return nil, fmt.Errorf("failed to add capabilities: %w", err) + } + + // for each node, merge the new capabilities with the existing ones and update the node + capsByPeer := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for p2pID, caps := range req.P2pToCapabilities { + caps, err := AppendCapabilities(lggr, req.Registry, req.Chain, []p2pkey.PeerID{p2pID}, caps) + if err != nil { + return nil, fmt.Errorf("failed to append capabilities for p2p %s: %w", p2pID, err) + } + capsByPeer[p2pID] = caps[p2pID] + } + + updateNodesReq := &UpdateNodesRequest{ + Chain: req.Chain, + Registry: req.Registry, + P2pToCapabilities: capsByPeer, + NopToNodes: req.NopToNodes, + } + resp, err := UpdateNodes(lggr, updateNodesReq) + if err != nil { + return nil, fmt.Errorf("failed to update nodes: %w", err) + } + return resp, nil +} diff --git a/deployment/keystone/changeset/append_node_capabilities_test.go b/deployment/keystone/changeset/internal/append_node_capabilities_test.go similarity index 74% rename from deployment/keystone/changeset/append_node_capabilities_test.go rename to deployment/keystone/changeset/internal/append_node_capabilities_test.go index e6b94b495fc..48a9af3e38d 100644 --- a/deployment/keystone/changeset/append_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities_test.go @@ -1,17 +1,16 @@ -package changeset_test +package internal_test import ( "testing" - "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" - kslib "github.com/smartcontractkit/chainlink/deployment/keystone" - "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" - kstest "github.com/smartcontractkit/chainlink/deployment/keystone/test" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + + kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) @@ -27,9 +26,9 @@ func TestAppendNodeCapabilities(t *testing.T) { }, }, } - nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "testNop"): []*kslib.P2PSignerEnc{ - &kslib.P2PSignerEnc{ + nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "testNop"): []*internal.P2PSignerEnc{ + &internal.P2PSignerEnc{ Signer: [32]byte{0: 1}, P2PKey: testPeerID(t, "0x1"), EncryptionPublicKey: [32]byte{7: 7, 13: 13}, @@ -42,7 +41,7 @@ func TestAppendNodeCapabilities(t *testing.T) { type args struct { lggr logger.Logger - req *changeset.AppendNodeCapabilitiesRequest + req *internal.AppendNodeCapabilitiesRequest initialState *kstest.SetupTestRegistryRequest } tests := []struct { @@ -55,7 +54,7 @@ func TestAppendNodeCapabilities(t *testing.T) { name: "invalid request", args: args{ lggr: lggr, - req: &changeset.AppendNodeCapabilitiesRequest{ + req: &internal.AppendNodeCapabilitiesRequest{ Chain: deployment.Chain{}, }, initialState: &kstest.SetupTestRegistryRequest{}, @@ -70,7 +69,7 @@ func TestAppendNodeCapabilities(t *testing.T) { P2pToCapabilities: initialp2pToCapabilities, NopToNodes: nopToNodes, }, - req: &changeset.AppendNodeCapabilitiesRequest{ + req: &internal.AppendNodeCapabilitiesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "0x1"): []kcr.CapabilitiesRegistryCapability{ { @@ -100,9 +99,9 @@ func TestAppendNodeCapabilities(t *testing.T) { tt.args.req.Registry = setupResp.Registry tt.args.req.Chain = setupResp.Chain - got, err := changeset.AppendNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) + got, err := internal.AppendNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) if (err != nil) != tt.wantErr { - t.Errorf("AppendNodeCapabilities() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("internal.AppendNodeCapabilities() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { @@ -119,17 +118,3 @@ func TestAppendNodeCapabilities(t *testing.T) { }) } } - -func testPeerID(t *testing.T, s string) p2pkey.PeerID { - var out [32]byte - b := []byte(s) - copy(out[:], b) - return p2pkey.PeerID(out) -} - -func testNop(t *testing.T, name string) kcr.CapabilitiesRegistryNodeOperator { - return kcr.CapabilitiesRegistryNodeOperator{ - Admin: common.HexToAddress("0xFFFFFFFF45297A703e4508186d4C1aa1BAf80000"), - Name: name, - } -} diff --git a/deployment/keystone/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go similarity index 92% rename from deployment/keystone/test/utils.go rename to deployment/keystone/changeset/internal/test/utils.go index 2e864fa7afc..64b08a70150 100644 --- a/deployment/keystone/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -14,19 +14,21 @@ import ( "github.com/smartcontractkit/chainlink/deployment/environment/memory" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + internal "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) type SetupTestRegistryRequest struct { P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc - DonToNodes map[string][]*kslib.P2PSignerEnc + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc + DonToNodes map[string][]*internal.P2PSignerEnc } type SetupTestRegistryResponse struct { - Registry *kcr.CapabilitiesRegistry - Chain deployment.Chain + Registry *kcr.CapabilitiesRegistry + Chain deployment.Chain + RegistrySelector uint64 } func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryRequest) *SetupTestRegistryResponse { @@ -63,7 +65,7 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR for p2pID := range req.P2pToCapabilities { initialp2pToCapabilities[p2pID] = vanillaCapabilities(registeredCapabilities) } - phonyRequest := &kslib.UpdateNodesRequest{ + phonyRequest := &internal.UpdateNodesRequest{ Chain: chain, Registry: registry, P2pToCapabilities: req.P2pToCapabilities, @@ -73,8 +75,9 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR require.NoError(t, err) addNodes(t, lggr, chain, registry, nodeParams) return &SetupTestRegistryResponse{ - Registry: registry, - Chain: chain, + Registry: registry, + Chain: chain, + RegistrySelector: chain.Selector, } } diff --git a/deployment/keystone/changeset/internal/update_node_capabilities.go b/deployment/keystone/changeset/internal/update_node_capabilities.go new file mode 100644 index 00000000000..3d3e1e80607 --- /dev/null +++ b/deployment/keystone/changeset/internal/update_node_capabilities.go @@ -0,0 +1,116 @@ +package internal + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +type UpdateNodeCapabilitiesImplRequest struct { + Chain deployment.Chain + Registry *kcr.CapabilitiesRegistry + + P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc +} + +func (req *UpdateNodeCapabilitiesImplRequest) Validate() error { + if len(req.P2pToCapabilities) == 0 { + return fmt.Errorf("p2pToCapabilities is empty") + } + if len(req.NopToNodes) == 0 { + return fmt.Errorf("nopToNodes is empty") + } + if req.Registry == nil { + return fmt.Errorf("registry is nil") + } + + return nil +} + +func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesImplRequest) (*UpdateNodesResponse, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("failed to validate request: %w", err) + } + // collect all the capabilities and add them to the registry + var capabilities []kcr.CapabilitiesRegistryCapability + for _, cap := range req.P2pToCapabilities { + capabilities = append(capabilities, cap...) + } + err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities) + if err != nil { + return nil, fmt.Errorf("failed to add capabilities: %w", err) + } + + updateNodesReq := &UpdateNodesRequest{ + Chain: req.Chain, + Registry: req.Registry, + P2pToCapabilities: req.P2pToCapabilities, + NopToNodes: req.NopToNodes, + } + resp, err := UpdateNodes(lggr, updateNodesReq) + if err != nil { + return nil, fmt.Errorf("failed to update nodes: %w", err) + } + return resp, nil +} + +/* +// AddCapabilities adds the capabilities to the registry +// it tries to add all capabilities in one go, if that fails, it falls back to adding them one by one +func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, chain deployment.Chain, capabilities []kcr.CapabilitiesRegistryCapability) error { + if len(capabilities) == 0 { + return nil + } + // dedup capabilities + var deduped []kcr.CapabilitiesRegistryCapability + seen := make(map[string]struct{}) + for _, cap := range capabilities { + if _, ok := seen[CapabilityID(cap)]; !ok { + seen[CapabilityID(cap)] = struct{}{} + deduped = append(deduped, cap) + } + } + + tx, err := registry.AddCapabilities(chain.DeployerKey, deduped) + if err != nil { + err = DecodeErr(kcr.CapabilitiesRegistryABI, err) + // no typed errors in the abi, so we have to do string matching + // try to add all capabilities in one go, if that fails, fall back to 1-by-1 + if !strings.Contains(err.Error(), "CapabilityAlreadyExists") { + return fmt.Errorf("failed to call AddCapabilities: %w", err) + } + lggr.Warnw("capabilities already exist, falling back to 1-by-1", "capabilities", deduped) + for _, cap := range deduped { + tx, err = registry.AddCapabilities(chain.DeployerKey, []kcr.CapabilitiesRegistryCapability{cap}) + if err != nil { + err = DecodeErr(kcr.CapabilitiesRegistryABI, err) + if strings.Contains(err.Error(), "CapabilityAlreadyExists") { + lggr.Warnw("capability already exists, skipping", "capability", cap) + continue + } + return fmt.Errorf("failed to call AddCapabilities for capability %v: %w", cap, err) + } + // 1-by-1 tx is pending and we need to wait for it to be mined + _, err = chain.Confirm(tx) + if err != nil { + return fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err) + } + lggr.Debugw("registered capability", "capability", cap) + + } + } else { + // the bulk add tx is pending and we need to wait for it to be mined + _, err = chain.Confirm(tx) + if err != nil { + return fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err) + } + lggr.Info("registered capabilities", "capabilities", deduped) + } + return nil +} +*/ diff --git a/deployment/keystone/changeset/update_node_capabilities_test.go b/deployment/keystone/changeset/internal/update_node_capabilities_test.go similarity index 87% rename from deployment/keystone/changeset/update_node_capabilities_test.go rename to deployment/keystone/changeset/internal/update_node_capabilities_test.go index 26dbedc602a..d90840f5d13 100644 --- a/deployment/keystone/changeset/update_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities_test.go @@ -1,4 +1,4 @@ -package changeset_test +package internal_test import ( "testing" @@ -8,9 +8,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" - kslib "github.com/smartcontractkit/chainlink/deployment/keystone" - "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" - kstest "github.com/smartcontractkit/chainlink/deployment/keystone/test" + //"github.com/smartcontractkit/chainlink/deployment/keystone/changeset" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) @@ -41,7 +41,7 @@ func TestUpdateNodeCapabilities(t *testing.T) { type args struct { lggr logger.Logger - req *changeset.UpdateNodeCapabilitiesRequest + req *kslib.UpdateNodeCapabilitiesImplRequest // chain and registry are set in the test setup initialState *kstest.SetupTestRegistryRequest } tests := []struct { @@ -54,7 +54,7 @@ func TestUpdateNodeCapabilities(t *testing.T) { name: "invalid request", args: args{ lggr: lggr, - req: &changeset.UpdateNodeCapabilitiesRequest{ + req: &kslib.UpdateNodeCapabilitiesImplRequest{ Chain: deployment.Chain{}, }, initialState: &kstest.SetupTestRegistryRequest{}, @@ -69,7 +69,7 @@ func TestUpdateNodeCapabilities(t *testing.T) { P2pToCapabilities: initialp2pToCapabilities, NopToNodes: nopToNodes, }, - req: &changeset.UpdateNodeCapabilitiesRequest{ + req: &kslib.UpdateNodeCapabilitiesImplRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "0x1"): []kcr.CapabilitiesRegistryCapability{ { @@ -93,13 +93,11 @@ func TestUpdateNodeCapabilities(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // chagen the name and args to be mor egeneral setupResp := kstest.SetupTestRegistry(t, lggr, tt.args.initialState) - tt.args.req.Registry = setupResp.Registry tt.args.req.Chain = setupResp.Chain - got, err := changeset.UpdateNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) + got, err := kslib.UpdateNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("UpdateNodeCapabilities() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/deployment/keystone/update_nodes.go b/deployment/keystone/changeset/internal/update_nodes.go similarity index 89% rename from deployment/keystone/update_nodes.go rename to deployment/keystone/changeset/internal/update_nodes.go index 1e87d3af311..48face7086b 100644 --- a/deployment/keystone/update_nodes.go +++ b/deployment/keystone/changeset/internal/update_nodes.go @@ -1,4 +1,4 @@ -package keystone +package internal import ( "errors" @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/deployment" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" ) type UpdateNodesRequest struct { @@ -64,7 +65,7 @@ func UpdateNodes(lggr logger.Logger, req *UpdateNodesRequest) (*UpdateNodesRespo } tx, err := req.Registry.UpdateNodes(req.Chain.DeployerKey, params) if err != nil { - err = DecodeErr(kcr.CapabilitiesRegistryABI, err) + err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) return nil, fmt.Errorf("failed to call UpdateNodes: %w", err) } @@ -113,8 +114,8 @@ func AppendCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, var deduped []kcr.CapabilitiesRegistryCapability seen := make(map[string]struct{}) for _, cap := range mergedCaps { - if _, ok := seen[CapabilityID(cap)]; !ok { - seen[CapabilityID(cap)] = struct{}{} + if _, ok := seen[kslib.CapabilityID(cap)]; !ok { + seen[kslib.CapabilityID(cap)] = struct{}{} deduped = append(deduped, cap) } } @@ -159,9 +160,9 @@ func makeNodeParams(registry *kcr.CapabilitiesRegistry, } hashedCaps := make([][32]byte, len(caps)) for i, cap := range caps { - hashedCap, exists := capMap[CapabilityID(cap)] + hashedCap, exists := capMap[kslib.CapabilityID(cap)] if !exists { - return nil, fmt.Errorf("capability id not found for %s", CapabilityID(cap)) + return nil, fmt.Errorf("capability id not found for %s", kslib.CapabilityID(cap)) } hashedCaps[i] = hashedCap } @@ -178,17 +179,11 @@ func makeNodeParams(registry *kcr.CapabilitiesRegistry, return out, nil } -// CapabilityID returns a unique id for the capability -// TODO: mv to chainlink-common? ref https://github.com/smartcontractkit/chainlink/blob/4fb06b4525f03c169c121a68defa9b13677f5f20/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol#L170 -func CapabilityID(c kcr.CapabilitiesRegistryCapability) string { - return fmt.Sprintf("%s@%s", c.LabelledName, c.Version) -} - -// fetchCapabilityIDs fetches the capability ids for the given capabilities +// fetchkslib.CapabilityIDs fetches the capability ids for the given capabilities func fetchCapabilityIDs(registry *kcr.CapabilitiesRegistry, caps []kcr.CapabilitiesRegistryCapability) (map[string][32]byte, error) { out := make(map[string][32]byte) for _, cap := range caps { - name := CapabilityID(cap) + name := kslib.CapabilityID(cap) if _, exists := out[name]; exists { continue } diff --git a/deployment/keystone/update_nodes_test.go b/deployment/keystone/changeset/internal/update_nodes_test.go similarity index 87% rename from deployment/keystone/update_nodes_test.go rename to deployment/keystone/changeset/internal/update_nodes_test.go index 24d40047823..3515ef13cbe 100644 --- a/deployment/keystone/update_nodes_test.go +++ b/deployment/keystone/changeset/internal/update_nodes_test.go @@ -1,4 +1,4 @@ -package keystone_test +package internal_test import ( "bytes" @@ -14,7 +14,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" - kstest "github.com/smartcontractkit/chainlink/deployment/keystone/test" + internal "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" "github.com/smartcontractkit/chainlink/deployment/environment/memory" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" @@ -24,8 +25,8 @@ import ( func Test_UpdateNodesRequest_validate(t *testing.T) { type fields struct { p2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - //nopToNodes map[uint32][]*kslib.P2PSigner - nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc + //nopToNodes map[uint32][]*internal.P2PSigner + nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc chain deployment.Chain registry *kcr.CapabilitiesRegistry } @@ -47,14 +48,14 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req := &kslib.UpdateNodesRequest{ + req := &internal.UpdateNodesRequest{ P2pToCapabilities: tt.fields.p2pToCapabilities, NopToNodes: tt.fields.nopToNodes, Chain: tt.fields.chain, Registry: tt.fields.registry, } if err := req.Validate(); (err != nil) != tt.wantErr { - t.Errorf("kslib.UpdateNodesRequest.validate() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("internal.UpdateNodesRequest.validate() error = %v, wantErr %v", err, tt.wantErr) } }) } @@ -67,19 +68,19 @@ func TestUpdateNodes(t *testing.T) { type args struct { lggr logger.Logger - req *kslib.UpdateNodesRequest + req *internal.UpdateNodesRequest } tests := []struct { name string args args - want *kslib.UpdateNodesResponse + want *internal.UpdateNodesResponse wantErr bool }{ { name: "one node, one capability", args: args{ lggr: lggr, - req: &kslib.UpdateNodesRequest{ + req: &internal.UpdateNodesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "peerID_1"): []kcr.CapabilitiesRegistryCapability{ { @@ -89,8 +90,8 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nop1"): []*kslib.P2PSignerEnc{ + NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop1"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 1: 2}, @@ -102,7 +103,7 @@ func TestUpdateNodes(t *testing.T) { Registry: nil, // set in test to ensure no conflicts }, }, - want: &kslib.UpdateNodesResponse{ + want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ { NodeOperatorId: 1, @@ -119,7 +120,7 @@ func TestUpdateNodes(t *testing.T) { name: "one node, two capabilities", args: args{ lggr: lggr, - req: &kslib.UpdateNodesRequest{ + req: &internal.UpdateNodesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "peerID_1"): []kcr.CapabilitiesRegistryCapability{ { @@ -134,8 +135,8 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nop1"): []*kslib.P2PSignerEnc{ + NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop1"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 1: 2}, @@ -147,7 +148,7 @@ func TestUpdateNodes(t *testing.T) { Registry: nil, // set in test to ensure no conflicts }, }, - want: &kslib.UpdateNodesResponse{ + want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ { NodeOperatorId: 1, @@ -171,7 +172,7 @@ func TestUpdateNodes(t *testing.T) { name: "twos node, one shared capability", args: args{ lggr: lggr, - req: &kslib.UpdateNodesRequest{ + req: &internal.UpdateNodesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "peerID_1"): []kcr.CapabilitiesRegistryCapability{ { @@ -188,15 +189,15 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nopA"): []*kslib.P2PSignerEnc{ + NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 31: 1}, EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, }, - testNop(t, "nopB"): []*kslib.P2PSignerEnc{ + testNop(t, "nopB"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_2"), Signer: [32]byte{0: 2, 31: 2}, @@ -208,7 +209,7 @@ func TestUpdateNodes(t *testing.T) { Registry: nil, // set in test to ensure no conflicts }, }, - want: &kslib.UpdateNodesResponse{ + want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ { NodeOperatorId: 1, @@ -232,7 +233,7 @@ func TestUpdateNodes(t *testing.T) { name: "twos node, different capabilities", args: args{ lggr: lggr, - req: &kslib.UpdateNodesRequest{ + req: &internal.UpdateNodesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "peerID_1"): []kcr.CapabilitiesRegistryCapability{ { @@ -249,15 +250,15 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nopA"): []*kslib.P2PSignerEnc{ + NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 31: 1}, EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, }, - testNop(t, "nopB"): []*kslib.P2PSignerEnc{ + testNop(t, "nopB"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_2"), Signer: [32]byte{0: 2, 31: 2}, @@ -269,7 +270,7 @@ func TestUpdateNodes(t *testing.T) { Registry: nil, // set in test to ensure no conflicts }, }, - want: &kslib.UpdateNodesResponse{ + want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ { NodeOperatorId: 1, @@ -319,7 +320,7 @@ func TestUpdateNodes(t *testing.T) { expectedCaps := capCache.AddCapabilities(tt.args.lggr, tt.args.req.Chain, registry, newCaps) expectedUpdatedCaps[p2p] = expectedCaps } - got, err := kslib.UpdateNodes(tt.args.lggr, tt.args.req) + got, err := internal.UpdateNodes(tt.args.lggr, tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("UpdateNodes() error = %v, wantErr %v", err, tt.wantErr) return @@ -376,8 +377,8 @@ func TestUpdateNodes(t *testing.T) { }, }, } - nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nopA"): []*kslib.P2PSignerEnc{ + nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 1: 2}, @@ -411,13 +412,13 @@ func TestUpdateNodes(t *testing.T) { _, err = chain.Confirm(tx) require.NoError(t, err) - var req = &kslib.UpdateNodesRequest{ + var req = &internal.UpdateNodesRequest{ P2pToCapabilities: p2pToCapabilitiesUpdated, NopToNodes: nopToNodes, Chain: chain, Registry: registry, } - _, err = kslib.UpdateNodes(lggr, req) + _, err = internal.UpdateNodes(lggr, req) require.NoError(t, err) info, err = registry.GetNode(&bind.CallOpts{}, testPeerID(t, "peerID_1")) require.NoError(t, err) @@ -425,7 +426,7 @@ func TestUpdateNodes(t *testing.T) { want := info.HashedCapabilityIds[0] // update again and ensure the result is the same - _, err = kslib.UpdateNodes(lggr, req) + _, err = internal.UpdateNodes(lggr, req) require.NoError(t, err) info, err = registry.GetNode(&bind.CallOpts{}, testPeerID(t, "peerID_1")) require.NoError(t, err) @@ -447,8 +448,8 @@ func TestAppendCapabilities(t *testing.T) { }, }, } - nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nop"): []*kslib.P2PSignerEnc{ + nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 1: 2}, @@ -483,7 +484,7 @@ func TestAppendCapabilities(t *testing.T) { CapabilityType: 0, }, } - appendedResp, err := kslib.AppendCapabilities(lggr, registry, chain, []p2pkey.PeerID{testPeerID(t, "peerID_1")}, newCaps) + appendedResp, err := internal.AppendCapabilities(lggr, registry, chain, []p2pkey.PeerID{testPeerID(t, "peerID_1")}, newCaps) require.NoError(t, err) require.Len(t, appendedResp, 1) gotCaps := appendedResp[testPeerID(t, "peerID_1")] @@ -496,7 +497,7 @@ func TestAppendCapabilities(t *testing.T) { } // trying to append an existing capability should not change the result - appendedResp2, err := kslib.AppendCapabilities(lggr, registry, chain, []p2pkey.PeerID{testPeerID(t, "peerID_1")}, newCaps) + appendedResp2, err := internal.AppendCapabilities(lggr, registry, chain, []p2pkey.PeerID{testPeerID(t, "peerID_1")}, newCaps) require.NoError(t, err) require.Len(t, appendedResp2, 1) gotCaps2 := appendedResp2[testPeerID(t, "peerID_1")] diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index 701aa260836..462a527273d 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -1,69 +1,113 @@ package changeset import ( + "encoding/json" "fmt" - "github.com/smartcontractkit/chainlink-common/pkg/logger" + chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) +var _ deployment.ChangeSet = UpdateNodeCapabilities + +type P2PSignerEnc = internal.P2PSignerEnc + type UpdateNodeCapabilitiesRequest struct { - Chain deployment.Chain - Registry *kcr.CapabilitiesRegistry + AddressBook deployment.AddressBook + RegistryChainSel uint64 P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *UpdateNodeCapabilitiesRequest) Validate() error { + if req.AddressBook == nil { + return fmt.Errorf("address book is nil") + } if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } if len(req.NopToNodes) == 0 { return fmt.Errorf("nopToNodes is empty") } - if req.Registry == nil { - return fmt.Errorf("registry is nil") + _, exists := chainsel.ChainBySelector(req.RegistryChainSel) + if !exists { + return fmt.Errorf("registry chain selector %d does not exist", req.RegistryChainSel) } return nil } -// UpdateNodeCapabilibity sets the capabilities of the node to the new capabilities. -// New capabilities are added to the onchain registry and the node is updated to host the new capabilities. -func UpdateNodeCapabilities(lggr logger.Logger, req *UpdateNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) { - _, err := updateNodeCapabilitiesImpl(lggr, req) - if err != nil { - return deployment.ChangesetOutput{}, err +type UpdateNodeCapabilitiesImplRequest struct { + Chain deployment.Chain + Registry *kcr.CapabilitiesRegistry + + P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc +} + +func (req *UpdateNodeCapabilitiesImplRequest) Validate() error { + if len(req.P2pToCapabilities) == 0 { + return fmt.Errorf("p2pToCapabilities is empty") + } + if len(req.NopToNodes) == 0 { + return fmt.Errorf("nopToNodes is empty") } - return deployment.ChangesetOutput{}, nil + if req.Registry == nil { + return fmt.Errorf("registry is nil") + } + + return nil } -func updateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesRequest) (*kslib.UpdateNodesResponse, error) { +func (req *UpdateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e deployment.Environment) (*internal.UpdateNodeCapabilitiesImplRequest, error) { if err := req.Validate(); err != nil { - return nil, fmt.Errorf("failed to validate request: %w", err) + return nil, fmt.Errorf("failed to validate UpdateNodeCapabilitiesRequest: %w", err) } - // collect all the capabilities and add them to the registry - var capabilities []kcr.CapabilitiesRegistryCapability - for _, cap := range req.P2pToCapabilities { - capabilities = append(capabilities, cap...) + registryChain, ok := e.Chains[req.RegistryChainSel] + if !ok { + return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities) + contracts, err := kslib.GetContractSets(&kslib.GetContractSetsRequest{ + Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain}, + AddressBook: req.AddressBook, + }) if err != nil { - return nil, fmt.Errorf("failed to add capabilities: %w", err) + return nil, fmt.Errorf("failed to get contract sets: %w", err) } - - updateNodesReq := &kslib.UpdateNodesRequest{ - Chain: req.Chain, - Registry: req.Registry, + registry := contracts.ContractSets[req.RegistryChainSel].CapabilitiesRegistry + if registry == nil { + return nil, fmt.Errorf("capabilities registry not found for chain %d", req.RegistryChainSel) + } + return &internal.UpdateNodeCapabilitiesImplRequest{ + Chain: registryChain, + Registry: registry, P2pToCapabilities: req.P2pToCapabilities, NopToNodes: req.NopToNodes, + }, nil +} + +// UpdateNodeCapabilities updates the capabilities of nodes in the registry +func UpdateNodeCapabilities(env deployment.Environment, config any) (deployment.ChangesetOutput, error) { + req, ok := config.(*UpdateNodeCapabilitiesRequest) + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid config type. want %T, got %T", &UpdateNodeCapabilitiesRequest{}, config) } - resp, err := kslib.UpdateNodes(lggr, updateNodesReq) + c, err := req.updateNodeCapabilitiesImplRequest(env) if err != nil { - return nil, fmt.Errorf("failed to update nodes: %w", err) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to convert request: %w", err) + } + + r, err := internal.UpdateNodeCapabilitiesImpl(env.Logger, c) + if err == nil { + b, err2 := json.Marshal(r) + if err2 != nil { + env.Logger.Debugf("Updated node capabilities '%s'", b) + } } - return resp, nil + return deployment.ChangesetOutput{}, err } diff --git a/deployment/keystone/changeset/view.go b/deployment/keystone/changeset/view.go index 684fcd7fc62..3f8d3c256f5 100644 --- a/deployment/keystone/changeset/view.go +++ b/deployment/keystone/changeset/view.go @@ -13,10 +13,10 @@ import ( var _ deployment.ViewState = ViewKeystone -func ViewKeystone(e deployment.Environment, ab deployment.AddressBook) (json.Marshaler, error) { +func ViewKeystone(e deployment.Environment) (json.Marshaler, error) { state, err := keystone.GetContractSets(&keystone.GetContractSetsRequest{ Chains: e.Chains, - AddressBook: ab, + AddressBook: e.ExistingAddresses, }) if err != nil { return nil, err @@ -42,7 +42,7 @@ func ViewKeystone(e deployment.Environment, ab deployment.AddressBook) (json.Mar if err != nil { return nil, err } - return view.KeystoneView{ + return &view.KeystoneView{ Chains: chainViews, Nops: nopsView, }, nil diff --git a/deployment/keystone/changeset/view_test.go b/deployment/keystone/changeset/view_test.go new file mode 100644 index 00000000000..2d4569dfbec --- /dev/null +++ b/deployment/keystone/changeset/view_test.go @@ -0,0 +1,39 @@ +package changeset + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" +) + +func TestKeystoneView(t *testing.T) { + t.Parallel() + env := memory.NewMemoryEnvironment(t, logger.Test(t), zapcore.DebugLevel, memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + }) + registryChain := env.AllChainSelectors()[0] + resp, err := DeployCapabilityRegistry(env, registryChain) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) + resp, err = DeployOCR3(env, registryChain) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) + resp, err = DeployForwarder(env, registryChain) + require.NoError(t, err) + require.NotNil(t, resp) + require.NoError(t, env.ExistingAddresses.Merge(resp.AddressBook)) + + a, err := ViewKeystone(env) + require.NoError(t, err) + b, err := a.MarshalJSON() + require.NoError(t, err) + require.NotEmpty(t, b) + t.Log(string(b)) +} diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index 487ac333eda..d6a1b2bf157 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -39,7 +39,6 @@ type ConfigureContractsRequest struct { Dons []DonCapabilities // externally sourced based on the environment OCR3Config *OracleConfigWithSecrets // TODO: probably should be a map of don to config; but currently we only have one wf don therefore one config - AddressBook deployment.AddressBook DoContractDeploy bool // if false, the contracts are assumed to be deployed and the address book is used } @@ -50,9 +49,6 @@ func (r ConfigureContractsRequest) Validate() error { if r.Env == nil { return errors.New("environment is nil") } - if r.AddressBook == nil { - return errors.New("address book is nil") - } if len(r.Dons) == 0 { return errors.New("no DONS") } @@ -75,7 +71,7 @@ func ConfigureContracts(ctx context.Context, lggr logger.Logger, req ConfigureCo return nil, fmt.Errorf("invalid request: %w", err) } - addrBook := req.AddressBook + addrBook := req.Env.ExistingAddresses if req.DoContractDeploy { contractDeployCS, err := DeployContracts(lggr, req.Env, req.RegistryChainSel) if err != nil { diff --git a/deployment/keystone/deploy_test.go b/deployment/keystone/deploy_test.go index b873f6bad0a..7ca8a98498b 100644 --- a/deployment/keystone/deploy_test.go +++ b/deployment/keystone/deploy_test.go @@ -68,13 +68,14 @@ func TestDeploy(t *testing.T) { // explicitly deploy the contracts cs, err := keystone.DeployContracts(lggr, env, registryChainSel) require.NoError(t, err) + // Deploy successful these are now part of our env. + require.NoError(t, env.ExistingAddresses.Merge(cs.AddressBook)) deployReq := keystone.ConfigureContractsRequest{ RegistryChainSel: registryChainSel, Env: env, OCR3Config: &ocr3Config, Dons: []keystone.DonCapabilities{wfDon, cwDon, assetDon}, - AddressBook: cs.AddressBook, DoContractDeploy: false, } deployResp, err := keystone.ConfigureContracts(ctx, lggr, deployReq) diff --git a/deployment/keystone/state.go b/deployment/keystone/state.go index 1b88438ecd8..e226d3b4b91 100644 --- a/deployment/keystone/state.go +++ b/deployment/keystone/state.go @@ -29,9 +29,9 @@ type ContractSet struct { } func (cs ContractSet) View() (view.KeystoneChainView, error) { - var out view.KeystoneChainView + out := view.NewKeystoneChainView() if cs.CapabilitiesRegistry != nil { - capRegView, err := common_v1_0.GenerateCapRegView(cs.CapabilitiesRegistry) + capRegView, err := common_v1_0.GenerateCapabilityRegistryView(cs.CapabilitiesRegistry) if err != nil { return view.KeystoneChainView{}, err } diff --git a/deployment/keystone/view/view.go b/deployment/keystone/view/view.go index 20d388d34b0..1320344b7ca 100644 --- a/deployment/keystone/view/view.go +++ b/deployment/keystone/view/view.go @@ -8,15 +8,23 @@ import ( ) type KeystoneChainView struct { - CapabilityRegistry map[string]common_v1_0.CapRegView `json:"capabilityRegistry,omitempty"` + CapabilityRegistry map[string]common_v1_0.CapabilityRegistryView `json:"capabilityRegistry,omitempty"` // TODO forwarders etc } +func NewKeystoneChainView() KeystoneChainView { + return KeystoneChainView{ + CapabilityRegistry: make(map[string]common_v1_0.CapabilityRegistryView), + } +} + type KeystoneView struct { Chains map[string]KeystoneChainView `json:"chains,omitempty"` Nops map[string]view.NopView `json:"nops,omitempty"` } func (v KeystoneView) MarshalJSON() ([]byte, error) { - return json.Marshal(v) + // Alias to avoid recursive calls + type Alias KeystoneView + return json.MarshalIndent(&struct{ Alias }{Alias: Alias(v)}, "", " ") } diff --git a/deployment/multiclient.go b/deployment/multiclient.go index 65f9e82bd01..dcda07ebb0b 100644 --- a/deployment/multiclient.go +++ b/deployment/multiclient.go @@ -103,9 +103,9 @@ func (mc *MultiClient) WaitMined(ctx context.Context, tx *types.Transaction) (*t resultCh := make(chan *types.Receipt) doneCh := make(chan struct{}) - waitMined := func(client *ethclient.Client, tx types.Transaction) { + waitMined := func(client *ethclient.Client, tx *types.Transaction) { mc.lggr.Debugf("Waiting for tx %s to be mined with client %v", tx.Hash().Hex(), client) - receipt, err := bind.WaitMined(ctx, client, &tx) + receipt, err := bind.WaitMined(ctx, client, tx) if err != nil { mc.lggr.Warnf("WaitMined error %v with client %v", err, client) return @@ -118,9 +118,7 @@ func (mc *MultiClient) WaitMined(ctx context.Context, tx *types.Transaction) (*t } for _, client := range append([]*ethclient.Client{mc.Client}, mc.Backups...) { - txn := tx - c := client - go waitMined(c, *txn) + go waitMined(client, tx) } var receipt *types.Receipt select { diff --git a/go.md b/go.md index f6215fbfebe..e2c5ae57bdd 100644 --- a/go.md +++ b/go.md @@ -92,6 +92,7 @@ flowchart LR flowchart LR subgraph chainlink chainlink/v2 + chainlink/deployment chainlink/integration-tests chainlink/load-tests chainlink/core/scripts @@ -119,7 +120,6 @@ flowchart LR end subgraph chainlink-testing-framework - chainlink-testing-framework/grafana chainlink-testing-framework/havoc chainlink-testing-framework/lib chainlink-testing-framework/lib/grafana diff --git a/go.mod b/go.mod index b72750556bb..739c9de9757 100644 --- a/go.mod +++ b/go.mod @@ -75,7 +75,7 @@ require ( github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-4b7e196370c4 github.com/smartcontractkit/chainlink-common v0.3.1-0.20241105163318-e1b7c81d582a github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241018134907-a00ba3729b5e diff --git a/go.sum b/go.sum index 2557fcf80e6..131231cbacd 100644 --- a/go.sum +++ b/go.sum @@ -1075,8 +1075,8 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 h1:+VR9yKhbz+iCtWnCabaCDP18lIqGnk7YvGQIbJYgDrs= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 h1:GWjim4uGGFbye4XbJP0cPAbARhc8u3cAJU8jLYy0mXM= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7 h1:AGi0kAtMRW1zl1h7sGw+3CKO4Nlev6iA08YfEcgJCGs= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241105151155-7fba27d48ac8 h1:201Qzwo1CdzrP5z4fiyUPeXGhtmzojmFRw/WWaI5TLc= diff --git a/integration-tests/ccip-tests/testsetups/test_helpers.go b/integration-tests/ccip-tests/testsetups/test_helpers.go index 3f12e9fd3c8..4acefca0975 100644 --- a/integration-tests/ccip-tests/testsetups/test_helpers.go +++ b/integration-tests/ccip-tests/testsetups/test_helpers.go @@ -88,7 +88,7 @@ func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) (ccipdeployment.De require.NoError(t, err) ab := deployment.NewMemoryAddressBook() - feeContracts, crConfig := ccipdeployment.DeployTestContracts(t, lggr, ab, homeChainSel, feedSel, chains) + crConfig := ccipdeployment.DeployTestContracts(t, lggr, ab, homeChainSel, feedSel, chains) // start the chainlink nodes with the CR address err = StartChainlinkNodes(t, envConfig, @@ -99,17 +99,16 @@ func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) (ccipdeployment.De e, don, err := devenv.NewEnvironment(ctx, lggr, *envConfig) require.NoError(t, err) require.NotNil(t, e) + e.ExistingAddresses = ab zeroLogLggr := logging.GetTestLogger(t) // fund the nodes FundNodes(t, zeroLogLggr, testEnv, cfg, don.PluginNodes()) return ccipdeployment.DeployedEnv{ - Ab: ab, - Env: *e, - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ReplayBlocks: replayBlocks, - FeeTokenContracts: feeContracts, + Env: *e, + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ReplayBlocks: replayBlocks, }, testEnv, cfg } @@ -119,18 +118,18 @@ func NewLocalDevEnvironmentWithRMN( numRmnNodes int, ) (ccipdeployment.DeployedEnv, devenv.RMNCluster) { tenv, dockerenv, _ := NewLocalDevEnvironment(t, lggr) - state, err := ccipdeployment.LoadOnchainState(tenv.Env, tenv.Ab) + state, err := ccipdeployment.LoadOnchainState(tenv.Env) require.NoError(t, err) // Deploy CCIP contracts. - err = ccipdeployment.DeployCCIPContracts(tenv.Env, tenv.Ab, ccipdeployment.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: ccipdeployment.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), - MCMSConfig: ccipdeployment.NewTestMCMSConfig(t, tenv.Env), - ExistingAddressBook: tenv.Ab, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + newAddresses := deployment.NewMemoryAddressBook() + err = ccipdeployment.DeployCCIPContracts(tenv.Env, newAddresses, ccipdeployment.DeployCCIPContractConfig{ + HomeChainSel: tenv.HomeChainSel, + FeedChainSel: tenv.FeedChainSel, + ChainsToDeploy: tenv.Env.AllChainSelectors(), + TokenConfig: ccipdeployment.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), + MCMSConfig: ccipdeployment.NewTestMCMSConfig(t, tenv.Env), + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) l := logging.GetTestLogger(t) @@ -181,7 +180,7 @@ func GenerateTestRMNConfig(t *testing.T, nRMNNodes int, tenv ccipdeployment.Depl bootstrappers := nodes.BootstrapLocators() // Just set all RMN nodes to support all chains. - state, err := ccipdeployment.LoadOnchainState(tenv.Env, tenv.Ab) + state, err := ccipdeployment.LoadOnchainState(tenv.Env) require.NoError(t, err) var chainParams []devenv.ChainParam var remoteChains []devenv.RemoteChains diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 054fbb27787..b223f729e8b 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -35,7 +35,7 @@ require ( github.com/slack-go/slack v0.15.0 github.com/smartcontractkit/chain-selectors v1.0.27 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7 github.com/smartcontractkit/chainlink-protos/job-distributor v0.4.0 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index d4f87ad83e7..df4ad771076 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1403,8 +1403,8 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 h1:+VR9yKhbz+iCtWnCabaCDP18lIqGnk7YvGQIbJYgDrs= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 h1:GWjim4uGGFbye4XbJP0cPAbARhc8u3cAJU8jLYy0mXM= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7 h1:AGi0kAtMRW1zl1h7sGw+3CKO4Nlev6iA08YfEcgJCGs= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index de72bc705e7..2cbbda250fd 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -64,7 +64,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 13e1974b17e..f6c81e4c500 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1392,8 +1392,8 @@ github.com/smartcontractkit/chain-selectors v1.0.27 h1:VE/ftX9Aae4gnw67yR1raKi+3 github.com/smartcontractkit/chain-selectors v1.0.27/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4 h1:+VR9yKhbz+iCtWnCabaCDP18lIqGnk7YvGQIbJYgDrs= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20241101132401-00090bf7feb4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4 h1:GWjim4uGGFbye4XbJP0cPAbARhc8u3cAJU8jLYy0mXM= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20241104130643-4b7e196370c4/go.mod h1:4adKaHNaxFsRvV/lYfqtbsWyyvIPUMLR0FdOJN/ljis= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7 h1:AGi0kAtMRW1zl1h7sGw+3CKO4Nlev6iA08YfEcgJCGs= github.com/smartcontractkit/chainlink-common v0.3.1-0.20241101093830-33711d0c3de7/go.mod h1:TQ9/KKXZ9vr8QAlUquqGpSvDCpR+DtABKPXZY4CiRns= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f h1:BwrIaQIx5Iy6eT+DfLhFfK2XqjxRm74mVdlX8gbu4dw= diff --git a/integration-tests/smoke/ccip_rmn_test.go b/integration-tests/smoke/ccip_rmn_test.go index 61c6df304b8..53bf25a3ac5 100644 --- a/integration-tests/smoke/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip_rmn_test.go @@ -61,7 +61,7 @@ func TestRMN(t *testing.T) { }) } - onChainState, err := ccipdeployment.LoadOnchainState(envWithRMN.Env, envWithRMN.Ab) + onChainState, err := ccipdeployment.LoadOnchainState(envWithRMN.Env) require.NoError(t, err) t.Logf("onChainState: %#v", onChainState) diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index 730484017fa..8d429b5a772 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -26,7 +26,7 @@ func TestInitialDeployOnLocal(t *testing.T) { tenv, _, _ := testsetups.NewLocalDevEnvironment(t, lggr) e := tenv.Env - state, err := ccdeploy.LoadOnchainState(tenv.Env, tenv.Ab) + state, err := ccdeploy.LoadOnchainState(tenv.Env) require.NoError(t, err) feeds := state.Chains[tenv.FeedChainSel].USDFeeds @@ -40,18 +40,17 @@ func TestInitialDeployOnLocal(t *testing.T) { ) // Apply migration output, err := changeset.InitialDeploy(tenv.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: tokenConfig, - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), - ExistingAddressBook: tenv.Ab, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + HomeChainSel: tenv.HomeChainSel, + FeedChainSel: tenv.FeedChainSel, + ChainsToDeploy: tenv.Env.AllChainSelectors(), + TokenConfig: tokenConfig, + MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) - require.NoError(t, tenv.Ab.Merge(output.AddressBook)) + require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) // Get new state after migration. - state, err = ccdeploy.LoadOnchainState(e, tenv.Ab) + state, err = ccdeploy.LoadOnchainState(e) require.NoError(t, err) // Ensure capreg logs are up to date. diff --git a/package.json b/package.json index 1da5d63f6d8..67ad90e3e6d 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "pnpm": ">=9" }, "devDependencies": { - "@changesets/changelog-github": "^0.4.8", "@changesets/cli": "~2.26.2", + "@changesets/get-github-info": "^0.6.0", "semver": "^7.6.3" } } \ No newline at end of file diff --git a/plugins/cmd/capabilities/log-event-trigger/main.go b/plugins/cmd/capabilities/log-event-trigger/main.go index d01485a743f..ab185adc57e 100644 --- a/plugins/cmd/capabilities/log-event-trigger/main.go +++ b/plugins/cmd/capabilities/log-event-trigger/main.go @@ -65,11 +65,11 @@ func (cs *LogEventTriggerGRPCService) Ready() error { } func (cs *LogEventTriggerGRPCService) HealthReport() map[string]error { - return nil + return map[string]error{cs.Name(): nil} } func (cs *LogEventTriggerGRPCService) Name() string { - return serviceName + return cs.s.Logger.Name() } func (cs *LogEventTriggerGRPCService) Infos(ctx context.Context) ([]capabilities.CapabilityInfo, error) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 260e6c79c07..5458e7e0f9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,12 @@ importers: .: devDependencies: - '@changesets/changelog-github': - specifier: ^0.4.8 - version: 0.4.8 '@changesets/cli': specifier: ~2.26.2 version: 2.26.2 + '@changesets/get-github-info': + specifier: ^0.6.0 + version: 0.6.0 semver: specifier: ^7.6.3 version: 7.6.3 @@ -45,9 +45,6 @@ packages: '@changesets/changelog-git@0.1.14': resolution: {integrity: sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==} - '@changesets/changelog-github@0.4.8': - resolution: {integrity: sha512-jR1DHibkMAb5v/8ym77E4AMNWZKB5NPzw5a5Wtqm1JepAuIF+hrKp2u04NKM14oBZhHglkCfrla9uq8ORnK/dw==} - '@changesets/cli@2.26.2': resolution: {integrity: sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==} hasBin: true @@ -61,8 +58,8 @@ packages: '@changesets/get-dependents-graph@1.3.6': resolution: {integrity: sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==} - '@changesets/get-github-info@0.5.2': - resolution: {integrity: sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==} + '@changesets/get-github-info@0.6.0': + resolution: {integrity: sha512-v/TSnFVXI8vzX9/w3DU2Ol+UlTZcu3m0kXTjTT4KlAdwSvwutcByYwyYn9hwerPWfPkT2JfpoX0KgvCEi8Q/SA==} '@changesets/get-release-plan@3.0.17': resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==} @@ -290,10 +287,6 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dotenv@8.6.0: - resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} - engines: {node: '>=10'} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1104,14 +1097,6 @@ snapshots: dependencies: '@changesets/types': 5.2.1 - '@changesets/changelog-github@0.4.8': - dependencies: - '@changesets/get-github-info': 0.5.2 - '@changesets/types': 5.2.1 - dotenv: 8.6.0 - transitivePeerDependencies: - - encoding - '@changesets/cli@2.26.2': dependencies: '@babel/runtime': 7.25.6 @@ -1170,7 +1155,7 @@ snapshots: fs-extra: 7.0.1 semver: 7.6.3 - '@changesets/get-github-info@0.5.2': + '@changesets/get-github-info@0.6.0': dependencies: dataloader: 1.4.0 node-fetch: 2.7.0 @@ -1463,8 +1448,6 @@ snapshots: dependencies: path-type: 4.0.0 - dotenv@8.6.0: {} - emoji-regex@8.0.0: {} enquirer@2.4.1: diff --git a/tools/bin/goreleaser_utils b/tools/bin/goreleaser_utils index b4b7f124ba7..52e37cefd51 100755 --- a/tools/bin/goreleaser_utils +++ b/tools/bin/goreleaser_utils @@ -27,7 +27,7 @@ before_hook() { # linux_arm64, rather than being suffixless on native platforms if [ "$GOARCH" = "arm64" ]; then if [ -d "$BIN_DIR/linux_arm64" ]; then - cp "$BIN_DIR/linux_arm64"/chainlink* "$PLUGIN_DIR" + cp "$BIN_DIR/linux_arm64_v8.0"/chainlink* "$PLUGIN_DIR" else cp "$BIN_DIR"/chainlink* "$PLUGIN_DIR" fi diff --git a/tools/bin/modgraph b/tools/bin/modgraph index e52baf77dda..14e6f78e81a 100755 --- a/tools/bin/modgraph +++ b/tools/bin/modgraph @@ -61,6 +61,7 @@ echo "## All modules flowchart LR subgraph chainlink chainlink/v2 + chainlink/deployment chainlink/integration-tests chainlink/load-tests chainlink/core/scripts @@ -88,7 +89,6 @@ flowchart LR end subgraph chainlink-testing-framework - chainlink-testing-framework/grafana chainlink-testing-framework/havoc chainlink-testing-framework/lib chainlink-testing-framework/lib/grafana