diff --git a/.changeset/blue-roses-brush.md b/.changeset/blue-roses-brush.md new file mode 100644 index 00000000000..57dc796c03b --- /dev/null +++ b/.changeset/blue-roses-brush.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#added Allow workflows to run without external registry configured diff --git a/.changeset/eight-bees-speak.md b/.changeset/eight-bees-speak.md new file mode 100644 index 00000000000..9c8ebe428d5 --- /dev/null +++ b/.changeset/eight-bees-speak.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#bugfix head reporter non-zero reporting period diff --git a/.changeset/hot-laws-deny.md b/.changeset/hot-laws-deny.md new file mode 100644 index 00000000000..d71783d1b78 --- /dev/null +++ b/.changeset/hot-laws-deny.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#internal log info on missed finalized head instead of returning an error diff --git a/.changeset/lucky-boats-run.md b/.changeset/lucky-boats-run.md new file mode 100644 index 00000000000..c5864a6e370 --- /dev/null +++ b/.changeset/lucky-boats-run.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Reporting all the token prices from the job spec for CCIP #updated diff --git a/.changeset/smart-pumas-collect.md b/.changeset/smart-pumas-collect.md new file mode 100644 index 00000000000..0748f170b9f --- /dev/null +++ b/.changeset/smart-pumas-collect.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#bugfix Fix incorrect error handling when registering a new feed manager diff --git a/.changeset/thick-mails-applaud.md b/.changeset/thick-mails-applaud.md new file mode 100644 index 00000000000..ceec9e64fd4 --- /dev/null +++ b/.changeset/thick-mails-applaud.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +Simplify how token and gas prices are stored in the database - user upsert instead of insert/delete flow #db_update diff --git a/.changeset/two-mugs-complain.md b/.changeset/two-mugs-complain.md new file mode 100644 index 00000000000..77cdcbfe9ec --- /dev/null +++ b/.changeset/two-mugs-complain.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Edited the Optimism Stack L1 Oracle to add support for Mantle #added diff --git a/.github/actions/goreleaser-build-sign-publish/README.md b/.github/actions/goreleaser-build-sign-publish/README.md index d6bf7e6fd4a..189578391f5 100644 --- a/.github/actions/goreleaser-build-sign-publish/README.md +++ b/.github/actions/goreleaser-build-sign-publish/README.md @@ -97,13 +97,17 @@ Following inputs can be used as `step.with` keys | Name | Type | Default | Description | | ---------------------------- | ------ | ------------------ | ----------------------------------------------------------------------- | -| `goreleaser-version` | String | `1.13.1` | `goreleaser` version | -| `zig-version` | String | `0.10.0` | `zig` version | -| `cosign-version` | String | `v1.13.1` | `cosign` version | +| `goreleaser-version` | String | `~> v2` | `goreleaser` version | +| `zig-version` | String | `0.10.1` | `zig` version | +| `cosign-version` | String | `v2.2.2` | `cosign` version | | `macos-sdk-dir` | String | `MacOSX12.3.sdk` | MacOSX sdk directory | | `enable-docker-publish` | Bool | `true` | Enable publishing of Docker images / manifests | | `docker-registry` | String | `localhost:5001` | Docker registry | +| `docker-image-name` | String | `chainlink` | Docker image name | +| `docker-image-tag` | String | `develop` | Docker image tag | | `enable-goreleaser-snapshot` | Bool | `false` | Enable goreleaser build / release snapshot | +| `enable-goreleaser-split` | Bool | `false` | Enable goreleaser build using split and merge | +| `goreleaser-split-arch` | String | `""` | The arch to build the image with - amd64, arm64 | | `goreleaser-exec` | String | `goreleaser` | The goreleaser executable, can invoke wrapper script | | `goreleaser-config` | String | `.goreleaser.yaml` | The goreleaser configuration yaml | | `enable-cosign` | Bool | `false` | Enable signing of Docker images | diff --git a/.github/actions/goreleaser-build-sign-publish/action.yml b/.github/actions/goreleaser-build-sign-publish/action.yml index c279c2f9290..cf9da323def 100644 --- a/.github/actions/goreleaser-build-sign-publish/action.yml +++ b/.github/actions/goreleaser-build-sign-publish/action.yml @@ -3,7 +3,7 @@ description: A composite action that allows building and publishing signed chain inputs: goreleaser-version: description: The goreleaser version - default: 1.23.0 + default: "~> v2" required: false goreleaser-key: description: The goreleaser key @@ -83,7 +83,7 @@ runs: with: go-version-file: "go.mod" - name: Setup goreleaser - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 with: distribution: goreleaser-pro install-only: true diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index 6a46e76f01e..3e1fd06b87f 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -161,7 +161,7 @@ runner-test-matrix: test_cmd: cd integration-tests/ && go test smoke/ocr2_test.go -timeout 30m -count=1 -test.parallel=6 -json pyroscope_env: ci-smoke-ocr2-plugins-evm-simulated test_env_vars: - E2E_TEST_CHAINLINK_VERSION: '{{ env.GITHUB_SHA_PLUGINS }}' # This is the chainlink version that has the plugins + E2E_TEST_CHAINLINK_VERSION: '{{ env.DEFAULT_CHAINLINK_PLUGINS_VERSION }}' # This is the chainlink version that has the plugins ENABLE_OTEL_TRACES: true # END: OCR tests @@ -215,7 +215,7 @@ runner-test-matrix: workflows: - PR E2E Core Tests - Nightly E2E Tests - test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_3_conditional_native|TestAutomationBasic/registry_2_3_conditional_link$" -test.parallel=3 -timeout 30m -count=1 -json + test_cmd: cd integration-tests/smoke && go test -test.run "^TestAutomationBasic/registry_2_3_conditional_native|TestAutomationBasic/registry_2_3_conditional_link$" -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-automation-evm-simulated - id: smoke/automation_test.go:^TestAutomationBasic/registry_2_3_logtrigger_native|TestAutomationBasic/registry_2_3_logtrigger_link$ @@ -375,7 +375,7 @@ runner-test-matrix: workflows: - PR E2E Core Tests - Nightly E2E Tests - test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperCheckPerformGasLimit$ -test.parallel=3 -timeout 30m -count=1 -json + test_cmd: cd integration-tests/smoke && go test -test.run ^TestKeeperCheckPerformGasLimit$ -test.parallel=2 -timeout 30m -count=1 -json pyroscope_env: ci-smoke-keeper-evm-simulated - id: smoke/keeper_test.go:^TestKeeperRegisterUpkeep$ @@ -823,7 +823,7 @@ runner-test-matrix: E2E_TEST_CHAINLINK_IMAGE: public.ecr.aws/chainlink/chainlink E2E_TEST_CHAINLINK_VERSION: '{{ env.LATEST_CHAINLINK_RELEASE_VERSION }}' E2E_TEST_CHAINLINK_UPGRADE_IMAGE: '{{ env.QA_CHAINLINK_IMAGE }}' - E2E_TEST_CHAINLINK_UPGRADE_VERSION: '{{ env.GITHUB_SHA }}' + E2E_TEST_CHAINLINK_UPGRADE_VERSION: '{{ env.DEFAULT_CHAINLINK_VERSION }}' # END: Other tests @@ -938,7 +938,6 @@ runner-test-matrix: test_env_type: docker runs_on: ubuntu-latest workflows: - - PR E2E CCIP Tests - Nightly E2E Tests test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPOffRampCapacityLimit$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: @@ -949,7 +948,6 @@ runner-test-matrix: test_env_type: docker runs_on: ubuntu-latest workflows: - - PR E2E CCIP Tests - Nightly E2E Tests test_cmd: cd integration-tests/ccip-tests/smoke && go test ccip_test.go -test.run ^TestSmokeCCIPOffRampAggRateLimit$ -timeout 30m -count=1 -test.parallel=1 -json test_env_vars: diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 8a96a66a68a..972228bc8ec 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -92,18 +92,21 @@ jobs: id: changes with: filters: | + github_ci_changes: + - '.github/workflows/integration-tests.yml' + - '.github/workflows/run-e2e-tests-reusable-workflow.yml' + - '.github/e2e-tests.yml' core_changes: - '**/*.go' - '**/*go.sum' - '**/*go.mod' - - '.github/workflows/integration-tests.yml' - '**/*Dockerfile' - 'core/**/migrations/*.sql' - 'core/**/config/**/*.toml' - 'integration-tests/**/*.toml' ccip_changes: - '**/*ccip*' - - '**/*ccip*/**' + - '**/*ccip*/**' - name: Ignore Filter On Workflow Dispatch if: ${{ github.event_name == 'workflow_dispatch' }} id: ignore-filter @@ -120,6 +123,7 @@ jobs: this-job-name: Check Paths That Require Tests To Run continue-on-error: true outputs: + github_ci_changes: ${{ steps.ignore-filter.outputs.changes || steps.changes.outputs.github_ci_changes }} core_changes: ${{ steps.ignore-filter.outputs.changes || steps.changes.outputs.core_changes }} ccip_changes: ${{ steps.ignore-filter.outputs.changes || steps.changes.outputs.ccip_changes }} @@ -193,7 +197,7 @@ jobs: needs: [changes, enforce-ctf-version] steps: - name: Collect Metrics - if: needs.changes.outputs.core_changes == 'true' || github.event_name == 'workflow_dispatch' + if: needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true' || github.event_name == 'workflow_dispatch' id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@d9da21a2747016b3e13de58c7d4115a3d5c97935 # v3.0.1 with: @@ -218,7 +222,7 @@ jobs: aws-region: ${{ secrets.AWS_REGION }} set-git-config: "true" - name: Build Chainlink Image - if: needs.changes.outputs.core_changes == 'true' || github.event_name == 'workflow_dispatch' + if: needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true' || github.event_name == 'workflow_dispatch' uses: ./.github/actions/build-chainlink-image with: tag_suffix: ${{ matrix.image.tag-suffix }} @@ -237,7 +241,7 @@ jobs: id-token: write contents: read needs: [build-chainlink, changes] - if: needs.changes.outputs.core_changes == 'true' + if: needs.changes.outputs.core_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true' uses: ./.github/workflows/run-e2e-tests-reusable-workflow.yml with: workflow_name: Run Core E2E Tests @@ -273,7 +277,7 @@ jobs: id-token: write contents: read needs: [build-chainlink, changes] - if: needs.changes.outputs.ccip_changes == 'true' + if: needs.changes.outputs.ccip_changes == 'true' || needs.changes.outputs.github_ci_changes == 'true' uses: ./.github/workflows/run-e2e-tests-reusable-workflow.yml with: workflow_name: Run CCIP E2E Tests diff --git a/.github/workflows/run-e2e-tests-reusable-workflow.yml b/.github/workflows/run-e2e-tests-reusable-workflow.yml index 92bc780f43a..182e0f31d58 100644 --- a/.github/workflows/run-e2e-tests-reusable-workflow.yml +++ b/.github/workflows/run-e2e-tests-reusable-workflow.yml @@ -10,11 +10,11 @@ on: type: string default: 'Run E2E Tests' chainlink_version: - description: 'Enter Chainlink version to use for the tests. Example: "v2.10.0" or sha' + description: 'Enter Chainlink version to use for the tests. Example: v2.10.0, develop or commit sha' required: false type: string chainlink_upgrade_version: - description: 'Enter Chainlink version to upgrade to for upgrade tests. Example: "v2.10.0" or sha' + description: 'Enter Chainlink version to use for the upgrade tests. Example: v2.10.0, develop or commit sha' required: false type: string test_ids: @@ -134,8 +134,8 @@ on: env: CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink QA_CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - GITHUB_SHA: ${{ github.sha }} - GITHUB_SHA_PLUGINS: ${{ github.sha }}-plugins + DEFAULT_CHAINLINK_VERSION: ${{ inputs.chainlink_version || github.sha }} + DEFAULT_CHAINLINK_PLUGINS_VERSION: ${{ inputs.chainlink_version != '' && format('{0}-plugins', inputs.chainlink_version) || format('{0}-plugins', github.sha) }} CHAINLINK_ENV_USER: ${{ github.actor }} CHAINLINK_COMMIT_SHA: ${{ inputs.evm-ref || github.sha }} SELECTED_NETWORKS: SIMULATED @@ -408,7 +408,7 @@ jobs: # Run Docker tests run-docker-tests: - name: Run ${{ matrix.tests.id }} + name: ${{ matrix.tests.id }} needs: [load-test-configurations, require-chainlink-image-versions-in-qa-ecr, require-chainlink-plugin-versions-in-qa-ecr, get_latest_chainlink_release_version] # Run when none of the needed jobs fail or are cancelled (skipped or successful jobs are ok) if: ${{ needs.load-test-configurations.outputs.run-docker-tests == 'true' && always() && !failure() && !cancelled() }} @@ -435,7 +435,7 @@ jobs: org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: ${{ inputs.workflow_name }} / Run ${{ matrix.tests.id }} + this-job-name: ${{ inputs.workflow_name }} / ${{ matrix.tests.id }} test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' continue-on-error: true @@ -626,7 +626,7 @@ jobs: run-k8s-runner-tests: needs: [load-test-configurations, prepare-remote-runner-test-image, require-chainlink-image-versions-in-qa-ecr, require-chainlink-plugin-versions-in-qa-ecr, get_latest_chainlink_release_version] if: ${{ needs.load-test-configurations.outputs.run-k8s-tests == 'true' && always() && !failure() && !cancelled() }} - name: Run ${{ matrix.tests.id }} + name: ${{ matrix.tests.id }} runs-on: ${{ matrix.tests.runs_on }} strategy: fail-fast: false @@ -650,7 +650,7 @@ jobs: org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} - this-job-name: ${{ inputs.workflow_name }} / Run ${{ matrix.tests.id }} + this-job-name: ${{ inputs.workflow_name }} / ${{ matrix.tests.id }} continue-on-error: true - name: Checkout repository @@ -769,7 +769,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": "${{ inputs.slack_notification_after_tests_name }} - ${{ steps.combine_results.outputs.result == 'failure' && 'Failed :x:' || steps.combine_results.outputs.result == 'cancelled' && 'Cancelled :warning:' || 'Passed :white_check_mark:' }}" + "text": "${{ inputs.slack_notification_after_tests_name }} - ${{ contains(join(needs.*.result, ','), 'failure') && 'Failed :x:' || contains(join(needs.*.result, ','), 'cancelled') && 'Cancelled :warning:' || 'Passed :white_check_mark:' }}" } }, { diff --git a/.github/workflows/run-nightly-e2e-tests.yml b/.github/workflows/run-nightly-e2e-tests.yml index ab12b36555f..6d7056ed04d 100644 --- a/.github/workflows/run-nightly-e2e-tests.yml +++ b/.github/workflows/run-nightly-e2e-tests.yml @@ -12,7 +12,7 @@ jobs: uses: ./.github/workflows/run-e2e-tests-reusable-workflow.yml with: chainlink_version: develop - test_workflow: Run Nightly E2E Tests + test_workflow: Nightly E2E Tests slack_notification_after_tests: true slack_notification_after_tests_channel_id: "#team-test-tooling-internal" slack_notification_after_tests_name: Nightly E2E Tests diff --git a/.github/workflows/solidity-foundry.yml b/.github/workflows/solidity-foundry.yml index c1da33dc6f4..ef643e32e87 100644 --- a/.github/workflows/solidity-foundry.yml +++ b/.github/workflows/solidity-foundry.yml @@ -74,16 +74,16 @@ jobs: list-files: 'shell' filters: | non_src: - - '.github/workflows/solidity-foundry.yml' + - '.github/workflows/solidity-foundry.yml' - 'contracts/foundry.toml' - 'contracts/gas-snapshots/*.gas-snapshot' - 'contracts/package.json' sol: - modified|added: 'contracts/src/v0.8/**/*.sol' sol_mod_only: - - modified: 'contracts/src/v0.8/**/!(*.t).sol' + - modified: 'contracts/src/v0.8/**/!(tests|mocks)/!(*.t).sol' not_test_sol: - - modified|added: 'contracts/src/v0.8/**/!(*.t).sol' + - modified|added: 'contracts/src/v0.8/**/!(tests|mocks)/!(*.t).sol' automation: - 'contracts/src/v0.8/automation/**/*.sol' ccip: @@ -112,7 +112,7 @@ jobs: - 'contracts/src/v0.8/transmission/**/*.sol' tests: - if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified == 'true' }} + if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.sol_modified_added == 'true' }} strategy: fail-fast: false matrix: @@ -425,8 +425,7 @@ jobs: fi else - echo "::error::Failed to find current commit's equivalent of $base_report (file $current_file doesn't exist, but should have been generated). Please check Slither logs." - exit 1 + echo "::warning::Failed to find current commit's equivalent of $base_report (file $current_report doesn't exist, but should have been generated). Please check Slither logs." fi done @@ -458,6 +457,49 @@ jobs: contracts/slither-reports-current retention-days: 7 + - name: Find Slither comment in the PR + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.0.0 + id: find-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: 'Static analysis results' + + - name: Extract job summary URL + id: job-summary-url + uses: pl-strflt/job-summary-url-action@df2d22c5351f73e0a187d20879854b8d98e6e001 # v1.0.0 + with: + job: 'Run static analysis' + + - name: Build Slither reports artifacts URL + id: build-slither-artifact-url + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ARTIFACTS=$(gh api -X GET repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts) + ARTIFACT_ID=$(echo "$ARTIFACTS" | jq '.artifacts[] | select(.name=="slither-reports-${{ github.sha }}") | .id') + echo "Artifact ID: $ARTIFACT_ID" + + slither_artifact_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID" + echo "slither_artifact_url=$slither_artifact_url" >> $GITHUB_OUTPUT + + - name: Create or update Slither comment in the PR + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Static analysis results are available + Hey @${{ github.event.push && github.event.push.pusher && github.event.push.pusher.username || github.actor }}, you can view Slither reports in the job summary [here](${{ steps.job-summary-url.outputs.job_summary_url }}) or download them as artifact [here](${{ steps.build-slither-artifact-url.outputs.slither_artifact_url }}). + + Please check them before merging and make sure you have addressed all issues. + edit-mode: replace + + - name: Remove temp artifacts + uses: geekyeggo/delete-artifact@24928e75e6e6590170563b8ddae9fac674508aa1 # v5.0 + with: + name: tmp-* + - name: Collect Metrics id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 @@ -469,11 +511,6 @@ jobs: this-job-name: Run static analysis continue-on-error: true - - name: Remove temp artifacts - uses: geekyeggo/delete-artifact@24928e75e6e6590170563b8ddae9fac674508aa1 # v5.0 - with: - name: tmp-* - solidity-forge-fmt: name: Forge fmt ${{ matrix.product.name }} if: ${{ needs.changes.outputs.non_src_changes == 'true' || needs.changes.outputs.not_test_sol_modified == 'true' }} diff --git a/.goreleaser.develop.yaml b/.goreleaser.develop.yaml index c3126b30f64..08d8e4de945 100644 --- a/.goreleaser.develop.yaml +++ b/.goreleaser.develop.yaml @@ -1,5 +1,7 @@ project_name: chainlink +version: 2 + env: - ZIG_EXEC={{ if index .Env "ZIG_EXEC" }}{{ .Env.ZIG_EXEC }}{{ else }}zig{{ end }} - IMAGE_PREFIX={{ if index .Env "IMAGE_PREFIX" }}{{ .Env.IMAGE_PREFIX }}{{ else }}localhost:5001{{ end }} @@ -196,7 +198,7 @@ checksum: name_template: "checksums.txt" snapshot: - name_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" + version_template: "{{ .Env.CHAINLINK_VERSION }}-{{ .ShortCommit }}" partial: by: target diff --git a/.goreleaser.devspace.yaml b/.goreleaser.devspace.yaml index bca65e90454..d0167b34cda 100644 --- a/.goreleaser.devspace.yaml +++ b/.goreleaser.devspace.yaml @@ -1,6 +1,7 @@ -## goreleaser <1.14.0 project_name: chainlink-devspace +version: 2 + env: - ZIG_EXEC={{ if index .Env "ZIG_EXEC" }}{{ .Env.ZIG_EXEC }}{{ else }}zig{{ end }} - IMAGE_LABEL_DESCRIPTION="node of the decentralized oracle network, bridging on and off-chain computation" @@ -75,7 +76,7 @@ checksum: name_template: "checksums.txt" snapshot: - name_template: '{{ .Env.CHAINLINK_VERSION }}-{{ .Runtime.Goarch }}-{{ .Now.Format "2006-01-02-15-04-05Z" }}' + version_template: '{{ .Env.CHAINLINK_VERSION }}-{{ .Runtime.Goarch }}-{{ .Now.Format "2006-01-02-15-04-05Z" }}' changelog: sort: asc diff --git a/.mockery.yaml b/.mockery.yaml index 87c27cd46a9..5cba66f3dad 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -529,6 +529,11 @@ packages: PriceGetter: config: mockname: "Mock{{ .InterfaceName }}" + filename: mock.go + AllTokensPriceGetter: + config: + mockname: "Mock{{ .InterfaceName }}" + filename: all_price_getter_mock.go github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/statuschecker: interfaces: CCIPTransactionStatusChecker: diff --git a/contracts/.changeset/eighty-ways-vanish.md b/contracts/.changeset/eighty-ways-vanish.md new file mode 100644 index 00000000000..3a48ca4e710 --- /dev/null +++ b/contracts/.changeset/eighty-ways-vanish.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +Publish a comment in PR mentioning the actor and informing her about avilability of Slither reports. diff --git a/contracts/.changeset/thirty-lamps-reply.md b/contracts/.changeset/thirty-lamps-reply.md new file mode 100644 index 00000000000..d8bcf8d4e83 --- /dev/null +++ b/contracts/.changeset/thirty-lamps-reply.md @@ -0,0 +1,5 @@ +--- +'@chainlink/contracts': patch +--- + +implement an auto registry for zksync with no forwarder interface change diff --git a/contracts/.solhintignore b/contracts/.solhintignore index 55d195c3059..bad1935442b 100644 --- a/contracts/.solhintignore +++ b/contracts/.solhintignore @@ -18,7 +18,6 @@ ./src/v0.8/automation/libraries/internal/Cron.sol ./src/v0.8/automation/AutomationForwarder.sol ./src/v0.8/automation/AutomationForwarderLogic.sol -./src/v0.8/automation/ZKSyncAutomationForwarder.sol ./src/v0.8/automation/interfaces/v2_2/IAutomationRegistryMaster.sol ./src/v0.8/automation/interfaces/v2_3/IAutomationRegistryMaster2_3.sol diff --git a/contracts/scripts/generate-zksync-automation-master-interface-v2_3.ts b/contracts/scripts/generate-zksync-automation-master-interface-v2_3.ts new file mode 100644 index 00000000000..1b91fd36361 --- /dev/null +++ b/contracts/scripts/generate-zksync-automation-master-interface-v2_3.ts @@ -0,0 +1,58 @@ +/** + * @description this script generates a master interface for interacting with the automation registry + * @notice run this script with pnpm ts-node ./scripts/generate-zksync-automation-master-interface-v2_3.ts + */ +import { ZKSyncAutomationRegistry2_3__factory as Registry } from '../typechain/factories/ZKSyncAutomationRegistry2_3__factory' +import { ZKSyncAutomationRegistryLogicA2_3__factory as RegistryLogicA } from '../typechain/factories/ZKSyncAutomationRegistryLogicA2_3__factory' +import { ZKSyncAutomationRegistryLogicB2_3__factory as RegistryLogicB } from '../typechain/factories/ZKSyncAutomationRegistryLogicB2_3__factory' +import { ZKSyncAutomationRegistryLogicC2_3__factory as RegistryLogicC } from '../typechain/factories/ZKSyncAutomationRegistryLogicC2_3__factory' +import { utils } from 'ethers' +import fs from 'fs' +import { exec } from 'child_process' + +const dest = 'src/v0.8/automation/interfaces/zksync' +const srcDest = `${dest}/IZKSyncAutomationRegistryMaster2_3.sol` +const tmpDest = `${dest}/tmp.txt` + +const combinedABI = [] +const abiSet = new Set() +const abis = [ + Registry.abi, + RegistryLogicA.abi, + RegistryLogicB.abi, + RegistryLogicC.abi, +] + +for (const abi of abis) { + for (const entry of abi) { + const id = utils.id(JSON.stringify(entry)) + if (!abiSet.has(id)) { + abiSet.add(id) + if ( + entry.type === 'function' && + (entry.name === 'checkUpkeep' || + entry.name === 'checkCallback' || + entry.name === 'simulatePerformUpkeep') + ) { + entry.stateMutability = 'view' // override stateMutability for check / callback / simulate functions + } + combinedABI.push(entry) + } + } +} + +const checksum = utils.id(abis.join('')) + +fs.writeFileSync(`${tmpDest}`, JSON.stringify(combinedABI)) + +const cmd = ` +cat ${tmpDest} | pnpm abi-to-sol --solidity-version ^0.8.4 --license MIT > ${srcDest} IZKSyncAutomationRegistryMaster2_3; +echo "// solhint-disable \n// abi-checksum: ${checksum}" | cat - ${srcDest} > ${tmpDest} && mv ${tmpDest} ${srcDest}; +pnpm prettier --write ${srcDest}; +` + +exec(cmd) + +console.log( + 'generated new master interface for zksync automation registry v2_3', +) diff --git a/contracts/scripts/native_solc_compile_all_automation b/contracts/scripts/native_solc_compile_all_automation index 29326a15c05..e189e78cb0f 100755 --- a/contracts/scripts/native_solc_compile_all_automation +++ b/contracts/scripts/native_solc_compile_all_automation @@ -93,7 +93,7 @@ compileContract automation/v2_2/AutomationUtils2_2.sol compileContract automation/interfaces/v2_2/IAutomationRegistryMaster.sol compileContract automation/chains/ArbitrumModule.sol compileContract automation/chains/ChainModuleBase.sol -compileContract automation/chains/OptimismModule.sol +compileContract automation/chains/OptimismModuleV2.sol compileContract automation/chains/ScrollModule.sol compileContract automation/interfaces/IChainModule.sol compileContract automation/interfaces/IAutomationV21PlusCommon.sol diff --git a/contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol b/contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol index cfbff1365e1..fd6eb3dee99 100644 --- a/contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol +++ b/contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol @@ -2,16 +2,19 @@ pragma solidity ^0.8.16; import {IAutomationRegistryConsumer} from "./interfaces/IAutomationRegistryConsumer.sol"; +import {GAS_BOUND_CALLER, IGasBoundCaller} from "./interfaces/zksync/IGasBoundCaller.sol"; -uint256 constant PERFORM_GAS_CUSHION = 5_000; +uint256 constant PERFORM_GAS_CUSHION = 50_000; /** - * @title AutomationForwarder is a relayer that sits between the registry and the customer's target contract + * @title ZKSyncAutomationForwarder is a relayer that sits between the registry and the customer's target contract * @dev The purpose of the forwarder is to give customers a consistent address to authorize against, * which stays consistent between migrations. The Forwarder also exposes the registry address, so that users who * want to programmatically interact with the registry (ie top up funds) can do so. */ contract ZKSyncAutomationForwarder { + error InvalidCaller(address); + /// @notice the user's target contract address address private immutable i_target; @@ -31,11 +34,14 @@ contract ZKSyncAutomationForwarder { * @param gasAmount is the amount of gas to use in the call * @param data is the 4 bytes function selector + arbitrary function data * @return success indicating whether the target call succeeded or failed + * @return gasUsed the total gas used from this forwarding call */ function forward(uint256 gasAmount, bytes memory data) external returns (bool success, uint256 gasUsed) { - if (msg.sender != address(s_registry)) revert(); + if (msg.sender != address(s_registry)) revert InvalidCaller(msg.sender); + + uint256 g1 = gasleft(); address target = i_target; - gasUsed = gasleft(); + assembly { let g := gas() // Compute g -= PERFORM_GAS_CUSHION and check for underflow @@ -52,10 +58,18 @@ contract ZKSyncAutomationForwarder { if iszero(extcodesize(target)) { revert(0, 0) } - // call with exact gas - success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0) } - gasUsed = gasUsed - gasleft(); + + bytes memory returnData; + // solhint-disable-next-line avoid-low-level-calls + (success, returnData) = GAS_BOUND_CALLER.delegatecall{gas: gasAmount}( + abi.encodeWithSelector(IGasBoundCaller.gasBoundCall.selector, target, gasAmount, data) + ); + uint256 pubdataGasSpent; + if (success) { + (, pubdataGasSpent) = abi.decode(returnData, (bytes, uint256)); + } + gasUsed = g1 - gasleft() + pubdataGasSpent; return (success, gasUsed); } @@ -63,7 +77,8 @@ contract ZKSyncAutomationForwarder { return i_target; } - fallback() external { + // solhint-disable-next-line no-complex-fallback + fallback() external payable { // copy to memory for assembly access address logic = i_logic; // copied directly from OZ's Proxy contract diff --git a/contracts/src/v0.8/automation/chains/ChainModuleBase.sol b/contracts/src/v0.8/automation/chains/ChainModuleBase.sol index 52829d43e5d..c595dbd6408 100644 --- a/contracts/src/v0.8/automation/chains/ChainModuleBase.sol +++ b/contracts/src/v0.8/automation/chains/ChainModuleBase.sol @@ -18,11 +18,11 @@ contract ChainModuleBase is IChainModule { return blockhash(n); } - function getCurrentL1Fee(uint256) external view virtual returns (uint256) { + function getCurrentL1Fee(uint256) external view virtual returns (uint256 l1Fee) { return 0; } - function getMaxL1Fee(uint256) external view virtual returns (uint256) { + function getMaxL1Fee(uint256) external view virtual returns (uint256 maxL1Fee) { return 0; } diff --git a/contracts/src/v0.8/automation/chains/OptimismModule.sol b/contracts/src/v0.8/automation/chains/OptimismModule.sol index c108bed008e..7a464294963 100644 --- a/contracts/src/v0.8/automation/chains/OptimismModule.sol +++ b/contracts/src/v0.8/automation/chains/OptimismModule.sol @@ -4,6 +4,10 @@ pragma solidity 0.8.19; import {OVM_GasPriceOracle} from "../../vendor/@eth-optimism/contracts/v0.8.9/contracts/L2/predeploys/OVM_GasPriceOracle.sol"; import {ChainModuleBase} from "./ChainModuleBase.sol"; +/** + * @notice This contract is deprecated. Please use OptimismModuleV2 which utilizes the most recent offerings from OP + * and can estimate L1 fee with much lower cost. + */ contract OptimismModule is ChainModuleBase { /// @dev OP_L1_DATA_FEE_PADDING includes 80 bytes for L1 data padding for Optimism and BASE bytes private constant OP_L1_DATA_FEE_PADDING = @@ -25,10 +29,15 @@ contract OptimismModule is ChainModuleBase { return _getL1Fee(dataSize); } + /* @notice this function provides an estimation for L1 fee incurred by calldata of a certain size + * @dev this function uses the getL1Fee function in OP gas price oracle. it estimates the exact L1 fee but it costs + * a lot of gas to call. + * @param dataSize the size of calldata + * @return l1Fee the L1 fee + */ function _getL1Fee(uint256 dataSize) internal view returns (uint256) { // fee is 4 per 0 byte, 16 per non-zero byte. Worst case we can have all non zero-bytes. // Instead of setting bytes to non-zero, we initialize 'new bytes' of length 4*dataSize to cover for zero bytes. - // this is the same as OP. bytes memory txCallData = new bytes(4 * dataSize); return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(txCallData, OP_L1_DATA_FEE_PADDING)); } diff --git a/contracts/src/v0.8/automation/chains/OptimismModuleV2.sol b/contracts/src/v0.8/automation/chains/OptimismModuleV2.sol new file mode 100644 index 00000000000..772b66cdf99 --- /dev/null +++ b/contracts/src/v0.8/automation/chains/OptimismModuleV2.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {GasPriceOracle as OVM_GasPriceOracle} from "../../vendor/@eth-optimism/contracts-bedrock/v0.17.3/src/L2/GasPriceOracle.sol"; +import {ChainModuleBase} from "./ChainModuleBase.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; + +/** + * @notice OptimismModuleV2 provides a cost-efficient way to get L1 fee on OP stack. + * After EIP-4844 is implemented in OP stack, the new OP upgrade includes a new function getL1FeeUpperBound to estimate + * the upper bound of current transaction's L1 fee. + */ +contract OptimismModuleV2 is ChainModuleBase, ConfirmedOwner { + error InvalidL1FeeCoefficient(uint8 coefficient); + event L1FeeCoefficientSet(uint8 coefficient); + + /// @dev OVM_GASPRICEORACLE_ADDR is the address of the OVM_GasPriceOracle precompile on Optimism. + /// @dev reference: https://community.optimism.io/docs/developers/build/transaction-fees/#estimating-the-l1-data-fee + address private constant OVM_GASPRICEORACLE_ADDR = address(0x420000000000000000000000000000000000000F); + OVM_GasPriceOracle private constant OVM_GASPRICEORACLE = OVM_GasPriceOracle(OVM_GASPRICEORACLE_ADDR); + + /// @dev L1 fee coefficient is used to account for the impact of data compression on the l1 fee + /// getL1FeeUpperBound returns the upper bound of l1 fee so this configurable coefficient will help + /// charge a predefined percentage of the upper bound. + uint8 private s_l1FeeCoefficient = 100; + uint256 private constant FIXED_GAS_OVERHEAD = 28_000; + uint256 private constant PER_CALLDATA_BYTE_GAS_OVERHEAD = 0; + + constructor() ConfirmedOwner(msg.sender) {} + + function getCurrentL1Fee(uint256 dataSize) external view override returns (uint256) { + return (s_l1FeeCoefficient * _getL1Fee(dataSize)) / 100; + } + + function getMaxL1Fee(uint256 dataSize) external view override returns (uint256) { + return _getL1Fee(dataSize); + } + + /* @notice this function provides an estimation for L1 fee incurred by calldata of a certain size + * @dev this function uses the newly provided getL1FeeUpperBound function in OP gas price oracle. this helps + * estimate L1 fee with much lower cost + * @param dataSize the size of calldata + * @return l1Fee the L1 fee + */ + function _getL1Fee(uint256 dataSize) internal view returns (uint256) { + return OVM_GASPRICEORACLE.getL1FeeUpperBound(dataSize); + } + + function getGasOverhead() + external + pure + override + returns (uint256 chainModuleFixedOverhead, uint256 chainModulePerByteOverhead) + { + return (FIXED_GAS_OVERHEAD, PER_CALLDATA_BYTE_GAS_OVERHEAD); + } + + /* @notice this function sets a new coefficient for L1 fee estimation. + * @dev this function can only be invoked by contract owner + * @param coefficient the new coefficient + */ + function setL1FeeCalculation(uint8 coefficient) external onlyOwner { + if (coefficient > 100) { + revert InvalidL1FeeCoefficient(coefficient); + } + + s_l1FeeCoefficient = coefficient; + + emit L1FeeCoefficientSet(coefficient); + } + + /* @notice this function returns the s_l1FeeCoefficient + * @return coefficient the current s_l1FeeCoefficient in effect + */ + function getL1FeeCoefficient() public view returns (uint256 coefficient) { + return s_l1FeeCoefficient; + } +} diff --git a/contracts/src/v0.8/automation/chains/ScrollModule.sol b/contracts/src/v0.8/automation/chains/ScrollModule.sol index 5a1373b3722..30fdc908d49 100644 --- a/contracts/src/v0.8/automation/chains/ScrollModule.sol +++ b/contracts/src/v0.8/automation/chains/ScrollModule.sol @@ -20,7 +20,7 @@ contract ScrollModule is ChainModuleBase, ConfirmedOwner { IScrollL1GasPriceOracle private constant SCROLL_ORACLE = IScrollL1GasPriceOracle(SCROLL_ORACLE_ADDR); /// @dev L1 fee coefficient can be applied to reduce possibly inflated gas cost - uint8 public s_l1FeeCoefficient = 100; + uint8 private s_l1FeeCoefficient = 100; uint256 private constant FIXED_GAS_OVERHEAD = 45_000; uint256 private constant PER_CALLDATA_BYTE_GAS_OVERHEAD = 170; @@ -34,7 +34,11 @@ contract ScrollModule is ChainModuleBase, ConfirmedOwner { return _getL1Fee(dataSize); } - function _getL1Fee(uint256 dataSize) internal view returns (uint256) { + /* @notice this function provides an estimation for L1 fee incurred by calldata of a certain size + * @param dataSize the size of calldata + * @return l1Fee the L1 fee + */ + function _getL1Fee(uint256 dataSize) internal view returns (uint256 l1Fee) { // fee is 4 per 0 byte, 16 per non-zero byte. Worst case we can have all non zero-bytes. // Instead of setting bytes to non-zero, we initialize 'new bytes' of length 4*dataSize to cover for zero bytes. // this is the same as OP. @@ -51,6 +55,10 @@ contract ScrollModule is ChainModuleBase, ConfirmedOwner { return (FIXED_GAS_OVERHEAD, PER_CALLDATA_BYTE_GAS_OVERHEAD); } + /* @notice this function sets a new coefficient for L1 fee estimation. + * @dev this function can only be invoked by contract owner + * @param coefficient the new coefficient + */ function setL1FeeCalculation(uint8 coefficient) external onlyOwner { if (coefficient > 100) { revert InvalidL1FeeCoefficient(coefficient); @@ -60,4 +68,11 @@ contract ScrollModule is ChainModuleBase, ConfirmedOwner { emit L1FeeCoefficientSet(coefficient); } + + /* @notice this function returns the s_l1FeeCoefficient + * @return coefficient the current s_l1FeeCoefficient in effect + */ + function getL1FeeCoefficient() public view returns (uint256 coefficient) { + return s_l1FeeCoefficient; + } } diff --git a/contracts/src/v0.8/automation/interfaces/IChainModule.sol b/contracts/src/v0.8/automation/interfaces/IChainModule.sol index 47d5b25111e..15e84f79848 100644 --- a/contracts/src/v0.8/automation/interfaces/IChainModule.sol +++ b/contracts/src/v0.8/automation/interfaces/IChainModule.sol @@ -2,26 +2,39 @@ pragma solidity ^0.8.0; interface IChainModule { - // retrieve the native block number of a chain. e.g. L2 block number on Arbitrum - function blockNumber() external view returns (uint256); + /* @notice this function provides the block number of current chain. + * @dev certain chains have its own function to retrieve block number, e.g. Arbitrum + * @return blockNumber the block number of the current chain. + */ + function blockNumber() external view returns (uint256 blockNumber); - // retrieve the native block hash of a chain. - function blockHash(uint256) external view returns (bytes32); + /* @notice this function provides the block hash of a block number. + * @dev this function can usually look back 256 blocks at most, unless otherwise specified + * @param blockNumber the block number + * @return blockHash the block hash of the input block number + */ + function blockHash(uint256 blockNumber) external view returns (bytes32 blockHash); - // retrieve the L1 data fee for a L2 transaction. it should return 0 for L1 chains and - // L2 chains which don't have L1 fee component. it uses msg.data to estimate L1 data so - // it must be used with a transaction. Return value in wei. - function getCurrentL1Fee(uint256 dataSize) external view returns (uint256); + /* @notice this function provides the L1 fee of current transaction. + * @dev retrieve the L1 data fee for a L2 transaction. it should return 0 for L1 chains. it should + * return 0 for L2 chains if they don't have L1 fee component. + * @param dataSize the calldata size of the current transaction + * @return l1Fee the L1 fee in wei incurred by calldata of this data size + */ + function getCurrentL1Fee(uint256 dataSize) external view returns (uint256 l1Fee); - // retrieve the L1 data fee for a L2 simulation. it should return 0 for L1 chains and - // L2 chains which don't have L1 fee component. Return value in wei. - function getMaxL1Fee(uint256 dataSize) external view returns (uint256); + /* @notice this function provides the max possible L1 fee of current transaction. + * @dev retrieve the max possible L1 data fee for a L2 transaction. it should return 0 for L1 chains. it should + * return 0 for L2 chains if they don't have L1 fee component. + * @param dataSize the calldata size of the current transaction + * @return maxL1Fee the max possible L1 fee in wei incurred by calldata of this data size + */ + function getMaxL1Fee(uint256 dataSize) external view returns (uint256 maxL1Fee); - // Returns an upper bound on execution gas cost for one invocation of blockNumber(), - // one invocation of blockHash() and one invocation of getCurrentL1Fee(). - // Returns two values, first value indicates a fixed cost and the second value is - // the cost per msg.data byte (As some chain module's getCurrentL1Fee execution cost - // scales with calldata size) + /* @notice this function provides the overheads of calling this chain module. + * @return chainModuleFixedOverhead the fixed overhead incurred by calling this chain module + * @return chainModulePerByteOverhead the fixed overhead per byte incurred by calling this chain module with calldata + */ function getGasOverhead() external view diff --git a/contracts/src/v0.8/automation/interfaces/zksync/IGasBoundCaller.sol b/contracts/src/v0.8/automation/interfaces/zksync/IGasBoundCaller.sol new file mode 100644 index 00000000000..9edb541f9aa --- /dev/null +++ b/contracts/src/v0.8/automation/interfaces/zksync/IGasBoundCaller.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +address constant GAS_BOUND_CALLER = address(0xc706EC7dfA5D4Dc87f29f859094165E8290530f5); + +interface IGasBoundCaller { + function gasBoundCall(address _to, uint256 _maxTotalGas, bytes calldata _data) external payable; +} diff --git a/contracts/src/v0.8/automation/interfaces/zksync/ISystemContext.sol b/contracts/src/v0.8/automation/interfaces/zksync/ISystemContext.sol new file mode 100644 index 00000000000..c8f480065c4 --- /dev/null +++ b/contracts/src/v0.8/automation/interfaces/zksync/ISystemContext.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +ISystemContext constant SYSTEM_CONTEXT_CONTRACT = ISystemContext(address(0x800b)); + +interface ISystemContext { + function gasPrice() external view returns (uint256); + function gasPerPubdataByte() external view returns (uint256 gasPerPubdataByte); + function getCurrentPubdataSpent() external view returns (uint256 currentPubdataSpent); +} diff --git a/contracts/src/v0.8/automation/interfaces/zksync/IZKSyncAutomationRegistryMaster2_3.sol b/contracts/src/v0.8/automation/interfaces/zksync/IZKSyncAutomationRegistryMaster2_3.sol new file mode 100644 index 00000000000..26b8a7d5c55 --- /dev/null +++ b/contracts/src/v0.8/automation/interfaces/zksync/IZKSyncAutomationRegistryMaster2_3.sol @@ -0,0 +1,441 @@ +// solhint-disable +// abi-checksum: 0x5857a77a981fcb60dbdac0700e68420cbe544249b20d9326d51c5ef8584c5dd7 +// SPDX-License-Identifier: MIT +// !! THIS FILE WAS AUTOGENERATED BY abi-to-sol v0.6.6. SEE SOURCE BELOW. !! +pragma solidity ^0.8.4; + +interface IZKSyncAutomationRegistryMaster2_3 { + error ArrayHasNoEntries(); + error CannotCancel(); + error CheckDataExceedsLimit(); + error ConfigDigestMismatch(); + error DuplicateEntry(); + error DuplicateSigners(); + error GasLimitCanOnlyIncrease(); + error GasLimitOutsideRange(); + error IncorrectNumberOfFaultyOracles(); + error IncorrectNumberOfSignatures(); + error IncorrectNumberOfSigners(); + error IndexOutOfRange(); + error InsufficientBalance(uint256 available, uint256 requested); + error InsufficientLinkLiquidity(); + error InvalidDataLength(); + error InvalidFeed(); + error InvalidPayee(); + error InvalidRecipient(); + error InvalidReport(); + error InvalidSigner(); + error InvalidToken(); + error InvalidTransmitter(); + error InvalidTrigger(); + error InvalidTriggerType(); + error MigrationNotPermitted(); + error MustSettleOffchain(); + error MustSettleOnchain(); + error NotAContract(); + error OnlyActiveSigners(); + error OnlyActiveTransmitters(); + error OnlyCallableByAdmin(); + error OnlyCallableByLINKToken(); + error OnlyCallableByOwnerOrAdmin(); + error OnlyCallableByOwnerOrRegistrar(); + error OnlyCallableByPayee(); + error OnlyCallableByProposedAdmin(); + error OnlyCallableByProposedPayee(); + error OnlyCallableByUpkeepPrivilegeManager(); + error OnlyFinanceAdmin(); + error OnlyPausedUpkeep(); + error OnlySimulatedBackend(); + error OnlyUnpausedUpkeep(); + error ParameterLengthError(); + error ReentrantCall(); + error RegistryPaused(); + error RepeatedSigner(); + error RepeatedTransmitter(); + error TargetCheckReverted(bytes reason); + error TooManyOracles(); + error TranscoderNotSet(); + error TransferFailed(); + error UpkeepAlreadyExists(); + error UpkeepCancelled(); + error UpkeepNotCanceled(); + error UpkeepNotNeeded(); + error ValueNotChanged(); + error ZeroAddressNotAllowed(); + event AdminPrivilegeConfigSet(address indexed admin, bytes privilegeConfig); + event BillingConfigOverridden(uint256 indexed id, ZKSyncAutomationRegistryBase2_3.BillingOverrides overrides); + event BillingConfigOverrideRemoved(uint256 indexed id); + event BillingConfigSet(address indexed token, ZKSyncAutomationRegistryBase2_3.BillingConfig config); + event CancelledUpkeepReport(uint256 indexed id, bytes trigger); + event ChainSpecificModuleUpdated(address newModule); + event ConfigSet( + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + address[] signers, + address[] transmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig + ); + event DedupKeyAdded(bytes32 indexed dedupKey); + event FeesWithdrawn(address indexed assetAddress, address indexed recipient, uint256 amount); + event FundsAdded(uint256 indexed id, address indexed from, uint96 amount); + event FundsWithdrawn(uint256 indexed id, uint256 amount, address to); + event InsufficientFundsUpkeepReport(uint256 indexed id, bytes trigger); + event NOPsSettledOffchain(address[] payees, uint256[] payments); + event OwnershipTransferRequested(address indexed from, address indexed to); + event OwnershipTransferred(address indexed from, address indexed to); + event Paused(address account); + event PayeesUpdated(address[] transmitters, address[] payees); + event PayeeshipTransferRequested(address indexed transmitter, address indexed from, address indexed to); + event PayeeshipTransferred(address indexed transmitter, address indexed from, address indexed to); + event PaymentWithdrawn(address indexed transmitter, uint256 indexed amount, address indexed to, address payee); + event ReorgedUpkeepReport(uint256 indexed id, bytes trigger); + event StaleUpkeepReport(uint256 indexed id, bytes trigger); + event Transmitted(bytes32 configDigest, uint32 epoch); + event Unpaused(address account); + event UpkeepAdminTransferRequested(uint256 indexed id, address indexed from, address indexed to); + event UpkeepAdminTransferred(uint256 indexed id, address indexed from, address indexed to); + event UpkeepCanceled(uint256 indexed id, uint64 indexed atBlockHeight); + event UpkeepCharged(uint256 indexed id, ZKSyncAutomationRegistryBase2_3.PaymentReceipt receipt); + event UpkeepCheckDataSet(uint256 indexed id, bytes newCheckData); + event UpkeepGasLimitSet(uint256 indexed id, uint96 gasLimit); + event UpkeepMigrated(uint256 indexed id, uint256 remainingBalance, address destination); + event UpkeepOffchainConfigSet(uint256 indexed id, bytes offchainConfig); + event UpkeepPaused(uint256 indexed id); + event UpkeepPerformed( + uint256 indexed id, + bool indexed success, + uint96 totalPayment, + uint256 gasUsed, + uint256 gasOverhead, + bytes trigger + ); + event UpkeepPrivilegeConfigSet(uint256 indexed id, bytes privilegeConfig); + event UpkeepReceived(uint256 indexed id, uint256 startingBalance, address importedFrom); + event UpkeepRegistered(uint256 indexed id, uint32 performGas, address admin); + event UpkeepTriggerConfigSet(uint256 indexed id, bytes triggerConfig); + event UpkeepUnpaused(uint256 indexed id); + fallback() external payable; + function acceptOwnership() external; + function fallbackTo() external view returns (address); + function latestConfigDetails() external view returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest); + function latestConfigDigestAndEpoch() external view returns (bool scanLogs, bytes32 configDigest, uint32 epoch); + function owner() external view returns (address); + function setConfig( + address[] memory signers, + address[] memory transmitters, + uint8 f, + bytes memory onchainConfigBytes, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external; + function setConfigTypeSafe( + address[] memory signers, + address[] memory transmitters, + uint8 f, + ZKSyncAutomationRegistryBase2_3.OnchainConfig memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig, + address[] memory billingTokens, + ZKSyncAutomationRegistryBase2_3.BillingConfig[] memory billingConfigs + ) external; + function transferOwnership(address to) external; + function transmit( + bytes32[3] memory reportContext, + bytes memory rawReport, + bytes32[] memory rs, + bytes32[] memory ss, + bytes32 rawVs + ) external; + function typeAndVersion() external view returns (string memory); + + function cancelUpkeep(uint256 id) external; + function migrateUpkeeps(uint256[] memory ids, address destination) external; + function onTokenTransfer(address sender, uint256 amount, bytes memory data) external; + function receiveUpkeeps(bytes memory encodedUpkeeps) external; + function registerUpkeep( + address target, + uint32 gasLimit, + address admin, + uint8 triggerType, + address billingToken, + bytes memory checkData, + bytes memory triggerConfig, + bytes memory offchainConfig + ) external returns (uint256 id); + + function acceptUpkeepAdmin(uint256 id) external; + function addFunds(uint256 id, uint96 amount) external payable; + function checkCallback( + uint256 id, + bytes[] memory values, + bytes memory extraData + ) external view returns (bool upkeepNeeded, bytes memory performData, uint8 upkeepFailureReason, uint256 gasUsed); + function checkUpkeep( + uint256 id, + bytes memory triggerData + ) + external + view + returns ( + bool upkeepNeeded, + bytes memory performData, + uint8 upkeepFailureReason, + uint256 gasUsed, + uint256 gasLimit, + uint256 fastGasWei, + uint256 linkUSD + ); + function checkUpkeep( + uint256 id + ) + external + view + returns ( + bool upkeepNeeded, + bytes memory performData, + uint8 upkeepFailureReason, + uint256 gasUsed, + uint256 gasLimit, + uint256 fastGasWei, + uint256 linkUSD + ); + function executeCallback( + uint256 id, + bytes memory payload + ) external returns (bool upkeepNeeded, bytes memory performData, uint8 upkeepFailureReason, uint256 gasUsed); + function pauseUpkeep(uint256 id) external; + function removeBillingOverrides(uint256 id) external; + function setBillingOverrides( + uint256 id, + ZKSyncAutomationRegistryBase2_3.BillingOverrides memory billingOverrides + ) external; + function setUpkeepCheckData(uint256 id, bytes memory newCheckData) external; + function setUpkeepGasLimit(uint256 id, uint32 gasLimit) external; + function setUpkeepOffchainConfig(uint256 id, bytes memory config) external; + function setUpkeepTriggerConfig(uint256 id, bytes memory triggerConfig) external; + function simulatePerformUpkeep( + uint256 id, + bytes memory performData + ) external view returns (bool success, uint256 gasUsed); + function transferUpkeepAdmin(uint256 id, address proposed) external; + function unpauseUpkeep(uint256 id) external; + function withdrawERC20Fees(address asset, address to, uint256 amount) external; + function withdrawFunds(uint256 id, address to) external; + function withdrawLink(address to, uint256 amount) external; + + function acceptPayeeship(address transmitter) external; + function disableOffchainPayments() external; + function getActiveUpkeepIDs(uint256 startIndex, uint256 maxCount) external view returns (uint256[] memory); + function getAdminPrivilegeConfig(address admin) external view returns (bytes memory); + function getAllowedReadOnlyAddress() external view returns (address); + function getAutomationForwarderLogic() external view returns (address); + function getAvailableERC20ForPayment(address billingToken) external view returns (uint256); + function getBalance(uint256 id) external view returns (uint96 balance); + function getBillingConfig( + address billingToken + ) external view returns (ZKSyncAutomationRegistryBase2_3.BillingConfig memory); + function getBillingOverrides( + uint256 upkeepID + ) external view returns (ZKSyncAutomationRegistryBase2_3.BillingOverrides memory); + function getBillingOverridesEnabled(uint256 upkeepID) external view returns (bool); + function getBillingToken(uint256 upkeepID) external view returns (address); + function getBillingTokenConfig( + address token + ) external view returns (ZKSyncAutomationRegistryBase2_3.BillingConfig memory); + function getBillingTokens() external view returns (address[] memory); + function getCancellationDelay() external pure returns (uint256); + function getChainModule() external view returns (address chainModule); + function getConditionalGasOverhead() external pure returns (uint256); + function getConfig() external view returns (ZKSyncAutomationRegistryBase2_3.OnchainConfig memory); + function getFallbackNativePrice() external view returns (uint256); + function getFastGasFeedAddress() external view returns (address); + function getForwarder(uint256 upkeepID) external view returns (address); + function getHotVars() external view returns (ZKSyncAutomationRegistryBase2_3.HotVars memory); + function getLinkAddress() external view returns (address); + function getLinkUSDFeedAddress() external view returns (address); + function getLogGasOverhead() external pure returns (uint256); + function getMaxPaymentForGas( + uint256 id, + uint8 triggerType, + uint32 gasLimit, + address billingToken + ) external view returns (uint96 maxPayment); + function getMinBalance(uint256 id) external view returns (uint96); + function getMinBalanceForUpkeep(uint256 id) external view returns (uint96 minBalance); + function getNativeUSDFeedAddress() external view returns (address); + function getNumUpkeeps() external view returns (uint256); + function getPayoutMode() external view returns (uint8); + function getPeerRegistryMigrationPermission(address peer) external view returns (uint8); + function getPerSignerGasOverhead() external pure returns (uint256); + function getReorgProtectionEnabled() external view returns (bool reorgProtectionEnabled); + function getReserveAmount(address billingToken) external view returns (uint256); + function getSignerInfo(address query) external view returns (bool active, uint8 index); + function getState() + external + view + returns ( + IAutomationV21PlusCommon.StateLegacy memory state, + IAutomationV21PlusCommon.OnchainConfigLegacy memory config, + address[] memory signers, + address[] memory transmitters, + uint8 f + ); + function getStorage() external view returns (ZKSyncAutomationRegistryBase2_3.Storage memory); + function getTransmitterInfo( + address query + ) external view returns (bool active, uint8 index, uint96 balance, uint96 lastCollected, address payee); + function getTransmittersWithPayees() + external + view + returns (ZKSyncAutomationRegistryBase2_3.TransmitterPayeeInfo[] memory); + function getTriggerType(uint256 upkeepId) external pure returns (uint8); + function getUpkeep(uint256 id) external view returns (IAutomationV21PlusCommon.UpkeepInfoLegacy memory upkeepInfo); + function getUpkeepPrivilegeConfig(uint256 upkeepId) external view returns (bytes memory); + function getUpkeepTriggerConfig(uint256 upkeepId) external view returns (bytes memory); + function getWrappedNativeTokenAddress() external view returns (address); + function hasDedupKey(bytes32 dedupKey) external view returns (bool); + function linkAvailableForPayment() external view returns (int256); + function pause() external; + function setAdminPrivilegeConfig(address admin, bytes memory newPrivilegeConfig) external; + function setPayees(address[] memory payees) external; + function setPeerRegistryMigrationPermission(address peer, uint8 permission) external; + function setUpkeepPrivilegeConfig(uint256 upkeepId, bytes memory newPrivilegeConfig) external; + function settleNOPsOffchain() external; + function supportsBillingToken(address token) external view returns (bool); + function transferPayeeship(address transmitter, address proposed) external; + function unpause() external; + function upkeepVersion() external pure returns (uint8); + function withdrawPayment(address from, address to) external; +} + +interface ZKSyncAutomationRegistryBase2_3 { + struct BillingOverrides { + uint32 gasFeePPB; + uint24 flatFeeMilliCents; + } + + struct BillingConfig { + uint32 gasFeePPB; + uint24 flatFeeMilliCents; + address priceFeed; + uint8 decimals; + uint256 fallbackPrice; + uint96 minSpend; + } + + struct PaymentReceipt { + uint96 gasChargeInBillingToken; + uint96 premiumInBillingToken; + uint96 gasReimbursementInJuels; + uint96 premiumInJuels; + address billingToken; + uint96 linkUSD; + uint96 nativeUSD; + uint96 billingUSD; + } + + struct OnchainConfig { + uint32 checkGasLimit; + uint32 maxPerformGas; + uint32 maxCheckDataSize; + address transcoder; + bool reorgProtectionEnabled; + uint24 stalenessSeconds; + uint32 maxPerformDataSize; + uint32 maxRevertDataSize; + address upkeepPrivilegeManager; + uint16 gasCeilingMultiplier; + address financeAdmin; + uint256 fallbackGasPrice; + uint256 fallbackLinkPrice; + uint256 fallbackNativePrice; + address[] registrars; + address chainModule; + } + + struct HotVars { + uint96 totalPremium; + uint32 latestEpoch; + uint24 stalenessSeconds; + uint16 gasCeilingMultiplier; + uint8 f; + bool paused; + bool reentrancyGuard; + bool reorgProtectionEnabled; + address chainModule; + } + + struct Storage { + address transcoder; + uint32 checkGasLimit; + uint32 maxPerformGas; + uint32 nonce; + address upkeepPrivilegeManager; + uint32 configCount; + uint32 latestConfigBlockNumber; + uint32 maxCheckDataSize; + address financeAdmin; + uint32 maxPerformDataSize; + uint32 maxRevertDataSize; + } + + struct TransmitterPayeeInfo { + address transmitterAddress; + address payeeAddress; + } +} + +interface IAutomationV21PlusCommon { + struct StateLegacy { + uint32 nonce; + uint96 ownerLinkBalance; + uint256 expectedLinkBalance; + uint96 totalPremium; + uint256 numUpkeeps; + uint32 configCount; + uint32 latestConfigBlockNumber; + bytes32 latestConfigDigest; + uint32 latestEpoch; + bool paused; + } + + struct OnchainConfigLegacy { + uint32 paymentPremiumPPB; + uint32 flatFeeMicroLink; + uint32 checkGasLimit; + uint24 stalenessSeconds; + uint16 gasCeilingMultiplier; + uint96 minUpkeepSpend; + uint32 maxPerformGas; + uint32 maxCheckDataSize; + uint32 maxPerformDataSize; + uint32 maxRevertDataSize; + uint256 fallbackGasPrice; + uint256 fallbackLinkPrice; + address transcoder; + address[] registrars; + address upkeepPrivilegeManager; + } + + struct UpkeepInfoLegacy { + address target; + uint32 performGas; + bytes checkData; + uint96 balance; + address admin; + uint64 maxValidBlocknumber; + uint32 lastPerformedBlockNumber; + uint96 amountSpent; + bool paused; + bytes offchainConfig; + } +} + +// THIS FILE WAS AUTOGENERATED FROM THE FOLLOWING ABI JSON: +/* +[{"inputs":[{"internalType":"contract ZKSyncAutomationRegistryLogicA2_3","name":"logicA","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ArrayHasNoEntries","type":"error"},{"inputs":[],"name":"CannotCancel","type":"error"},{"inputs":[],"name":"CheckDataExceedsLimit","type":"error"},{"inputs":[],"name":"ConfigDigestMismatch","type":"error"},{"inputs":[],"name":"DuplicateEntry","type":"error"},{"inputs":[],"name":"DuplicateSigners","type":"error"},{"inputs":[],"name":"GasLimitCanOnlyIncrease","type":"error"},{"inputs":[],"name":"GasLimitOutsideRange","type":"error"},{"inputs":[],"name":"IncorrectNumberOfFaultyOracles","type":"error"},{"inputs":[],"name":"IncorrectNumberOfSignatures","type":"error"},{"inputs":[],"name":"IncorrectNumberOfSigners","type":"error"},{"inputs":[],"name":"IndexOutOfRange","type":"error"},{"inputs":[{"internalType":"uint256","name":"available","type":"uint256"},{"internalType":"uint256","name":"requested","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InsufficientLinkLiquidity","type":"error"},{"inputs":[],"name":"InvalidDataLength","type":"error"},{"inputs":[],"name":"InvalidFeed","type":"error"},{"inputs":[],"name":"InvalidPayee","type":"error"},{"inputs":[],"name":"InvalidRecipient","type":"error"},{"inputs":[],"name":"InvalidReport","type":"error"},{"inputs":[],"name":"InvalidSigner","type":"error"},{"inputs":[],"name":"InvalidToken","type":"error"},{"inputs":[],"name":"InvalidTransmitter","type":"error"},{"inputs":[],"name":"InvalidTrigger","type":"error"},{"inputs":[],"name":"InvalidTriggerType","type":"error"},{"inputs":[],"name":"MigrationNotPermitted","type":"error"},{"inputs":[],"name":"MustSettleOffchain","type":"error"},{"inputs":[],"name":"MustSettleOnchain","type":"error"},{"inputs":[],"name":"NotAContract","type":"error"},{"inputs":[],"name":"OnlyActiveSigners","type":"error"},{"inputs":[],"name":"OnlyActiveTransmitters","type":"error"},{"inputs":[],"name":"OnlyCallableByAdmin","type":"error"},{"inputs":[],"name":"OnlyCallableByLINKToken","type":"error"},{"inputs":[],"name":"OnlyCallableByOwnerOrAdmin","type":"error"},{"inputs":[],"name":"OnlyCallableByOwnerOrRegistrar","type":"error"},{"inputs":[],"name":"OnlyCallableByPayee","type":"error"},{"inputs":[],"name":"OnlyCallableByProposedAdmin","type":"error"},{"inputs":[],"name":"OnlyCallableByProposedPayee","type":"error"},{"inputs":[],"name":"OnlyCallableByUpkeepPrivilegeManager","type":"error"},{"inputs":[],"name":"OnlyFinanceAdmin","type":"error"},{"inputs":[],"name":"OnlyPausedUpkeep","type":"error"},{"inputs":[],"name":"OnlySimulatedBackend","type":"error"},{"inputs":[],"name":"OnlyUnpausedUpkeep","type":"error"},{"inputs":[],"name":"ParameterLengthError","type":"error"},{"inputs":[],"name":"ReentrantCall","type":"error"},{"inputs":[],"name":"RegistryPaused","type":"error"},{"inputs":[],"name":"RepeatedSigner","type":"error"},{"inputs":[],"name":"RepeatedTransmitter","type":"error"},{"inputs":[{"internalType":"bytes","name":"reason","type":"bytes"}],"name":"TargetCheckReverted","type":"error"},{"inputs":[],"name":"TooManyOracles","type":"error"},{"inputs":[],"name":"TranscoderNotSet","type":"error"},{"inputs":[],"name":"TransferFailed","type":"error"},{"inputs":[],"name":"UpkeepAlreadyExists","type":"error"},{"inputs":[],"name":"UpkeepCancelled","type":"error"},{"inputs":[],"name":"UpkeepNotCanceled","type":"error"},{"inputs":[],"name":"UpkeepNotNeeded","type":"error"},{"inputs":[],"name":"ValueNotChanged","type":"error"},{"inputs":[],"name":"ZeroAddressNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":false,"internalType":"bytes","name":"privilegeConfig","type":"bytes"}],"name":"AdminPrivilegeConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"components":[{"internalType":"uint32","name":"gasFeePPB","type":"uint32"},{"internalType":"uint24","name":"flatFeeMilliCents","type":"uint24"}],"indexed":false,"internalType":"struct ZKSyncAutomationRegistryBase2_3.BillingOverrides","name":"overrides","type":"tuple"}],"name":"BillingConfigOverridden","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"BillingConfigOverrideRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IERC20Metadata","name":"token","type":"address"},{"components":[{"internalType":"uint32","name":"gasFeePPB","type":"uint32"},{"internalType":"uint24","name":"flatFeeMilliCents","type":"uint24"},{"internalType":"contract AggregatorV3Interface","name":"priceFeed","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"fallbackPrice","type":"uint256"},{"internalType":"uint96","name":"minSpend","type":"uint96"}],"indexed":false,"internalType":"struct ZKSyncAutomationRegistryBase2_3.BillingConfig","name":"config","type":"tuple"}],"name":"BillingConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"CancelledUpkeepReport","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newModule","type":"address"}],"name":"ChainSpecificModuleUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"previousConfigBlockNumber","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"configCount","type":"uint64"},{"indexed":false,"internalType":"address[]","name":"signers","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"transmitters","type":"address[]"},{"indexed":false,"internalType":"uint8","name":"f","type":"uint8"},{"indexed":false,"internalType":"bytes","name":"onchainConfig","type":"bytes"},{"indexed":false,"internalType":"uint64","name":"offchainConfigVersion","type":"uint64"},{"indexed":false,"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"ConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"dedupKey","type":"bytes32"}],"name":"DedupKeyAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"assetAddress","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FeesWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint96","name":"amount","type":"uint96"}],"name":"FundsAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"FundsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"InsufficientFundsUpkeepReport","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"payees","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"payments","type":"uint256[]"}],"name":"NOPsSettledOffchain","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"transmitters","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"payees","type":"address[]"}],"name":"PayeesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"transmitter","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"PayeeshipTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"transmitter","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"PayeeshipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"transmitter","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"address","name":"payee","type":"address"}],"name":"PaymentWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"ReorgedUpkeepReport","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"StaleUpkeepReport","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"epoch","type":"uint32"}],"name":"Transmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"UpkeepAdminTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"UpkeepAdminTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"uint64","name":"atBlockHeight","type":"uint64"}],"name":"UpkeepCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"components":[{"internalType":"uint96","name":"gasChargeInBillingToken","type":"uint96"},{"internalType":"uint96","name":"premiumInBillingToken","type":"uint96"},{"internalType":"uint96","name":"gasReimbursementInJuels","type":"uint96"},{"internalType":"uint96","name":"premiumInJuels","type":"uint96"},{"internalType":"contract IERC20Metadata","name":"billingToken","type":"address"},{"internalType":"uint96","name":"linkUSD","type":"uint96"},{"internalType":"uint96","name":"nativeUSD","type":"uint96"},{"internalType":"uint96","name":"billingUSD","type":"uint96"}],"indexed":false,"internalType":"struct ZKSyncAutomationRegistryBase2_3.PaymentReceipt","name":"receipt","type":"tuple"}],"name":"UpkeepCharged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newCheckData","type":"bytes"}],"name":"UpkeepCheckDataSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint96","name":"gasLimit","type":"uint96"}],"name":"UpkeepGasLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"remainingBalance","type":"uint256"},{"indexed":false,"internalType":"address","name":"destination","type":"address"}],"name":"UpkeepMigrated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"UpkeepOffchainConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"UpkeepPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"bool","name":"success","type":"bool"},{"indexed":false,"internalType":"uint96","name":"totalPayment","type":"uint96"},{"indexed":false,"internalType":"uint256","name":"gasUsed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasOverhead","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"UpkeepPerformed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"privilegeConfig","type":"bytes"}],"name":"UpkeepPrivilegeConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"startingBalance","type":"uint256"},{"indexed":false,"internalType":"address","name":"importedFrom","type":"address"}],"name":"UpkeepReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint32","name":"performGas","type":"uint32"},{"indexed":false,"internalType":"address","name":"admin","type":"address"}],"name":"UpkeepRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"triggerConfig","type":"bytes"}],"name":"UpkeepTriggerConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"UpkeepUnpaused","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fallbackTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestConfigDetails","outputs":[{"internalType":"uint32","name":"configCount","type":"uint32"},{"internalType":"uint32","name":"blockNumber","type":"uint32"},{"internalType":"bytes32","name":"configDigest","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestConfigDigestAndEpoch","outputs":[{"internalType":"bool","name":"scanLogs","type":"bool"},{"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"internalType":"uint32","name":"epoch","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"signers","type":"address[]"},{"internalType":"address[]","name":"transmitters","type":"address[]"},{"internalType":"uint8","name":"f","type":"uint8"},{"internalType":"bytes","name":"onchainConfigBytes","type":"bytes"},{"internalType":"uint64","name":"offchainConfigVersion","type":"uint64"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"setConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"signers","type":"address[]"},{"internalType":"address[]","name":"transmitters","type":"address[]"},{"internalType":"uint8","name":"f","type":"uint8"},{"components":[{"internalType":"uint32","name":"checkGasLimit","type":"uint32"},{"internalType":"uint32","name":"maxPerformGas","type":"uint32"},{"internalType":"uint32","name":"maxCheckDataSize","type":"uint32"},{"internalType":"address","name":"transcoder","type":"address"},{"internalType":"bool","name":"reorgProtectionEnabled","type":"bool"},{"internalType":"uint24","name":"stalenessSeconds","type":"uint24"},{"internalType":"uint32","name":"maxPerformDataSize","type":"uint32"},{"internalType":"uint32","name":"maxRevertDataSize","type":"uint32"},{"internalType":"address","name":"upkeepPrivilegeManager","type":"address"},{"internalType":"uint16","name":"gasCeilingMultiplier","type":"uint16"},{"internalType":"address","name":"financeAdmin","type":"address"},{"internalType":"uint256","name":"fallbackGasPrice","type":"uint256"},{"internalType":"uint256","name":"fallbackLinkPrice","type":"uint256"},{"internalType":"uint256","name":"fallbackNativePrice","type":"uint256"},{"internalType":"address[]","name":"registrars","type":"address[]"},{"internalType":"contract IChainModule","name":"chainModule","type":"address"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.OnchainConfig","name":"onchainConfig","type":"tuple"},{"internalType":"uint64","name":"offchainConfigVersion","type":"uint64"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"},{"internalType":"contract IERC20Metadata[]","name":"billingTokens","type":"address[]"},{"components":[{"internalType":"uint32","name":"gasFeePPB","type":"uint32"},{"internalType":"uint24","name":"flatFeeMilliCents","type":"uint24"},{"internalType":"contract AggregatorV3Interface","name":"priceFeed","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"fallbackPrice","type":"uint256"},{"internalType":"uint96","name":"minSpend","type":"uint96"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.BillingConfig[]","name":"billingConfigs","type":"tuple[]"}],"name":"setConfigTypeSafe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[3]","name":"reportContext","type":"bytes32[3]"},{"internalType":"bytes","name":"rawReport","type":"bytes"},{"internalType":"bytes32[]","name":"rs","type":"bytes32[]"},{"internalType":"bytes32[]","name":"ss","type":"bytes32[]"},{"internalType":"bytes32","name":"rawVs","type":"bytes32"}],"name":"transmit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"typeAndVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ZKSyncAutomationRegistryLogicB2_3","name":"logicB","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"cancelUpkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"address","name":"destination","type":"address"}],"name":"migrateUpkeeps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onTokenTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedUpkeeps","type":"bytes"}],"name":"receiveUpkeeps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint32","name":"gasLimit","type":"uint32"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"enum ZKSyncAutomationRegistryBase2_3.Trigger","name":"triggerType","type":"uint8"},{"internalType":"contract IERC20Metadata","name":"billingToken","type":"address"},{"internalType":"bytes","name":"checkData","type":"bytes"},{"internalType":"bytes","name":"triggerConfig","type":"bytes"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"registerUpkeep","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ZKSyncAutomationRegistryLogicC2_3","name":"logicC","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"acceptUpkeepAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint96","name":"amount","type":"uint96"}],"name":"addFunds","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes[]","name":"values","type":"bytes[]"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"checkCallback","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"},{"internalType":"enum ZKSyncAutomationRegistryBase2_3.UpkeepFailureReason","name":"upkeepFailureReason","type":"uint8"},{"internalType":"uint256","name":"gasUsed","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"triggerData","type":"bytes"}],"name":"checkUpkeep","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"},{"internalType":"enum ZKSyncAutomationRegistryBase2_3.UpkeepFailureReason","name":"upkeepFailureReason","type":"uint8"},{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"fastGasWei","type":"uint256"},{"internalType":"uint256","name":"linkUSD","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"checkUpkeep","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"},{"internalType":"enum ZKSyncAutomationRegistryBase2_3.UpkeepFailureReason","name":"upkeepFailureReason","type":"uint8"},{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"fastGasWei","type":"uint256"},{"internalType":"uint256","name":"linkUSD","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"executeCallback","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"},{"internalType":"enum ZKSyncAutomationRegistryBase2_3.UpkeepFailureReason","name":"upkeepFailureReason","type":"uint8"},{"internalType":"uint256","name":"gasUsed","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"pauseUpkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"removeBillingOverrides","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"components":[{"internalType":"uint32","name":"gasFeePPB","type":"uint32"},{"internalType":"uint24","name":"flatFeeMilliCents","type":"uint24"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.BillingOverrides","name":"billingOverrides","type":"tuple"}],"name":"setBillingOverrides","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"newCheckData","type":"bytes"}],"name":"setUpkeepCheckData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint32","name":"gasLimit","type":"uint32"}],"name":"setUpkeepGasLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"config","type":"bytes"}],"name":"setUpkeepOffchainConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"triggerConfig","type":"bytes"}],"name":"setUpkeepTriggerConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"performData","type":"bytes"}],"name":"simulatePerformUpkeep","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"uint256","name":"gasUsed","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"proposed","type":"address"}],"name":"transferUpkeepAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"unpauseUpkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"asset","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawERC20Fees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawLink","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"link","type":"address"},{"internalType":"address","name":"linkUSDFeed","type":"address"},{"internalType":"address","name":"nativeUSDFeed","type":"address"},{"internalType":"address","name":"fastGasFeed","type":"address"},{"internalType":"address","name":"automationForwarderLogic","type":"address"},{"internalType":"address","name":"allowedReadOnlyAddress","type":"address"},{"internalType":"enum ZKSyncAutomationRegistryBase2_3.PayoutMode","name":"payoutMode","type":"uint8"},{"internalType":"address","name":"wrappedNativeTokenAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"transmitter","type":"address"}],"name":"acceptPayeeship","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"disableOffchainPayments","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"startIndex","type":"uint256"},{"internalType":"uint256","name":"maxCount","type":"uint256"}],"name":"getActiveUpkeepIDs","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"getAdminPrivilegeConfig","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllowedReadOnlyAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAutomationForwarderLogic","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"billingToken","type":"address"}],"name":"getAvailableERC20ForPayment","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getBalance","outputs":[{"internalType":"uint96","name":"balance","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"billingToken","type":"address"}],"name":"getBillingConfig","outputs":[{"components":[{"internalType":"uint32","name":"gasFeePPB","type":"uint32"},{"internalType":"uint24","name":"flatFeeMilliCents","type":"uint24"},{"internalType":"contract AggregatorV3Interface","name":"priceFeed","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"fallbackPrice","type":"uint256"},{"internalType":"uint96","name":"minSpend","type":"uint96"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.BillingConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepID","type":"uint256"}],"name":"getBillingOverrides","outputs":[{"components":[{"internalType":"uint32","name":"gasFeePPB","type":"uint32"},{"internalType":"uint24","name":"flatFeeMilliCents","type":"uint24"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.BillingOverrides","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepID","type":"uint256"}],"name":"getBillingOverridesEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepID","type":"uint256"}],"name":"getBillingToken","outputs":[{"internalType":"contract IERC20Metadata","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"token","type":"address"}],"name":"getBillingTokenConfig","outputs":[{"components":[{"internalType":"uint32","name":"gasFeePPB","type":"uint32"},{"internalType":"uint24","name":"flatFeeMilliCents","type":"uint24"},{"internalType":"contract AggregatorV3Interface","name":"priceFeed","type":"address"},{"internalType":"uint8","name":"decimals","type":"uint8"},{"internalType":"uint256","name":"fallbackPrice","type":"uint256"},{"internalType":"uint96","name":"minSpend","type":"uint96"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.BillingConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBillingTokens","outputs":[{"internalType":"contract IERC20Metadata[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCancellationDelay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getChainModule","outputs":[{"internalType":"contract IChainModule","name":"chainModule","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConditionalGasOverhead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getConfig","outputs":[{"components":[{"internalType":"uint32","name":"checkGasLimit","type":"uint32"},{"internalType":"uint32","name":"maxPerformGas","type":"uint32"},{"internalType":"uint32","name":"maxCheckDataSize","type":"uint32"},{"internalType":"address","name":"transcoder","type":"address"},{"internalType":"bool","name":"reorgProtectionEnabled","type":"bool"},{"internalType":"uint24","name":"stalenessSeconds","type":"uint24"},{"internalType":"uint32","name":"maxPerformDataSize","type":"uint32"},{"internalType":"uint32","name":"maxRevertDataSize","type":"uint32"},{"internalType":"address","name":"upkeepPrivilegeManager","type":"address"},{"internalType":"uint16","name":"gasCeilingMultiplier","type":"uint16"},{"internalType":"address","name":"financeAdmin","type":"address"},{"internalType":"uint256","name":"fallbackGasPrice","type":"uint256"},{"internalType":"uint256","name":"fallbackLinkPrice","type":"uint256"},{"internalType":"uint256","name":"fallbackNativePrice","type":"uint256"},{"internalType":"address[]","name":"registrars","type":"address[]"},{"internalType":"contract IChainModule","name":"chainModule","type":"address"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.OnchainConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFallbackNativePrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFastGasFeedAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepID","type":"uint256"}],"name":"getForwarder","outputs":[{"internalType":"contract IAutomationForwarder","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getHotVars","outputs":[{"components":[{"internalType":"uint96","name":"totalPremium","type":"uint96"},{"internalType":"uint32","name":"latestEpoch","type":"uint32"},{"internalType":"uint24","name":"stalenessSeconds","type":"uint24"},{"internalType":"uint16","name":"gasCeilingMultiplier","type":"uint16"},{"internalType":"uint8","name":"f","type":"uint8"},{"internalType":"bool","name":"paused","type":"bool"},{"internalType":"bool","name":"reentrancyGuard","type":"bool"},{"internalType":"bool","name":"reorgProtectionEnabled","type":"bool"},{"internalType":"contract IChainModule","name":"chainModule","type":"address"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.HotVars","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLinkAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLinkUSDFeedAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLogGasOverhead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"enum ZKSyncAutomationRegistryBase2_3.Trigger","name":"triggerType","type":"uint8"},{"internalType":"uint32","name":"gasLimit","type":"uint32"},{"internalType":"contract IERC20Metadata","name":"billingToken","type":"address"}],"name":"getMaxPaymentForGas","outputs":[{"internalType":"uint96","name":"maxPayment","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getMinBalance","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getMinBalanceForUpkeep","outputs":[{"internalType":"uint96","name":"minBalance","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNativeUSDFeedAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNumUpkeeps","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPayoutMode","outputs":[{"internalType":"enum ZKSyncAutomationRegistryBase2_3.PayoutMode","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"peer","type":"address"}],"name":"getPeerRegistryMigrationPermission","outputs":[{"internalType":"enum ZKSyncAutomationRegistryBase2_3.MigrationPermission","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPerSignerGasOverhead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getReorgProtectionEnabled","outputs":[{"internalType":"bool","name":"reorgProtectionEnabled","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"billingToken","type":"address"}],"name":"getReserveAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"query","type":"address"}],"name":"getSignerInfo","outputs":[{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint8","name":"index","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getState","outputs":[{"components":[{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"uint96","name":"ownerLinkBalance","type":"uint96"},{"internalType":"uint256","name":"expectedLinkBalance","type":"uint256"},{"internalType":"uint96","name":"totalPremium","type":"uint96"},{"internalType":"uint256","name":"numUpkeeps","type":"uint256"},{"internalType":"uint32","name":"configCount","type":"uint32"},{"internalType":"uint32","name":"latestConfigBlockNumber","type":"uint32"},{"internalType":"bytes32","name":"latestConfigDigest","type":"bytes32"},{"internalType":"uint32","name":"latestEpoch","type":"uint32"},{"internalType":"bool","name":"paused","type":"bool"}],"internalType":"struct IAutomationV21PlusCommon.StateLegacy","name":"state","type":"tuple"},{"components":[{"internalType":"uint32","name":"paymentPremiumPPB","type":"uint32"},{"internalType":"uint32","name":"flatFeeMicroLink","type":"uint32"},{"internalType":"uint32","name":"checkGasLimit","type":"uint32"},{"internalType":"uint24","name":"stalenessSeconds","type":"uint24"},{"internalType":"uint16","name":"gasCeilingMultiplier","type":"uint16"},{"internalType":"uint96","name":"minUpkeepSpend","type":"uint96"},{"internalType":"uint32","name":"maxPerformGas","type":"uint32"},{"internalType":"uint32","name":"maxCheckDataSize","type":"uint32"},{"internalType":"uint32","name":"maxPerformDataSize","type":"uint32"},{"internalType":"uint32","name":"maxRevertDataSize","type":"uint32"},{"internalType":"uint256","name":"fallbackGasPrice","type":"uint256"},{"internalType":"uint256","name":"fallbackLinkPrice","type":"uint256"},{"internalType":"address","name":"transcoder","type":"address"},{"internalType":"address[]","name":"registrars","type":"address[]"},{"internalType":"address","name":"upkeepPrivilegeManager","type":"address"}],"internalType":"struct IAutomationV21PlusCommon.OnchainConfigLegacy","name":"config","type":"tuple"},{"internalType":"address[]","name":"signers","type":"address[]"},{"internalType":"address[]","name":"transmitters","type":"address[]"},{"internalType":"uint8","name":"f","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStorage","outputs":[{"components":[{"internalType":"address","name":"transcoder","type":"address"},{"internalType":"uint32","name":"checkGasLimit","type":"uint32"},{"internalType":"uint32","name":"maxPerformGas","type":"uint32"},{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"address","name":"upkeepPrivilegeManager","type":"address"},{"internalType":"uint32","name":"configCount","type":"uint32"},{"internalType":"uint32","name":"latestConfigBlockNumber","type":"uint32"},{"internalType":"uint32","name":"maxCheckDataSize","type":"uint32"},{"internalType":"address","name":"financeAdmin","type":"address"},{"internalType":"uint32","name":"maxPerformDataSize","type":"uint32"},{"internalType":"uint32","name":"maxRevertDataSize","type":"uint32"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.Storage","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"query","type":"address"}],"name":"getTransmitterInfo","outputs":[{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint8","name":"index","type":"uint8"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint96","name":"lastCollected","type":"uint96"},{"internalType":"address","name":"payee","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTransmittersWithPayees","outputs":[{"components":[{"internalType":"address","name":"transmitterAddress","type":"address"},{"internalType":"address","name":"payeeAddress","type":"address"}],"internalType":"struct ZKSyncAutomationRegistryBase2_3.TransmitterPayeeInfo[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepId","type":"uint256"}],"name":"getTriggerType","outputs":[{"internalType":"enum ZKSyncAutomationRegistryBase2_3.Trigger","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getUpkeep","outputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint32","name":"performGas","type":"uint32"},{"internalType":"bytes","name":"checkData","type":"bytes"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"uint64","name":"maxValidBlocknumber","type":"uint64"},{"internalType":"uint32","name":"lastPerformedBlockNumber","type":"uint32"},{"internalType":"uint96","name":"amountSpent","type":"uint96"},{"internalType":"bool","name":"paused","type":"bool"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"internalType":"struct IAutomationV21PlusCommon.UpkeepInfoLegacy","name":"upkeepInfo","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepId","type":"uint256"}],"name":"getUpkeepPrivilegeConfig","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepId","type":"uint256"}],"name":"getUpkeepTriggerConfig","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWrappedNativeTokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"dedupKey","type":"bytes32"}],"name":"hasDedupKey","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"linkAvailableForPayment","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"bytes","name":"newPrivilegeConfig","type":"bytes"}],"name":"setAdminPrivilegeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"payees","type":"address[]"}],"name":"setPayees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"peer","type":"address"},{"internalType":"enum ZKSyncAutomationRegistryBase2_3.MigrationPermission","name":"permission","type":"uint8"}],"name":"setPeerRegistryMigrationPermission","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepId","type":"uint256"},{"internalType":"bytes","name":"newPrivilegeConfig","type":"bytes"}],"name":"setUpkeepPrivilegeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"settleNOPsOffchain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20Metadata","name":"token","type":"address"}],"name":"supportsBillingToken","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"transmitter","type":"address"},{"internalType":"address","name":"proposed","type":"address"}],"name":"transferPayeeship","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"upkeepVersion","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawPayment","outputs":[],"stateMutability":"nonpayable","type":"function"}] +*/ diff --git a/contracts/src/v0.8/automation/test/v2_3_zksync/BaseTest.t.sol b/contracts/src/v0.8/automation/test/v2_3_zksync/BaseTest.t.sol new file mode 100644 index 00000000000..cde05ab3a22 --- /dev/null +++ b/contracts/src/v0.8/automation/test/v2_3_zksync/BaseTest.t.sol @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "forge-std/Test.sol"; + +import {LinkToken} from "../../../shared/token/ERC677/LinkToken.sol"; +import {ERC20Mock} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/mocks/ERC20Mock.sol"; +import {ERC20Mock6Decimals} from "../../mocks/ERC20Mock6Decimals.sol"; +import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol"; +import {AutomationForwarderLogic} from "../../AutomationForwarderLogic.sol"; +import {UpkeepTranscoder5_0 as Transcoder} from "../../v2_3/UpkeepTranscoder5_0.sol"; +import {ZKSyncAutomationRegistry2_3} from "../../v2_3_zksync/ZKSyncAutomationRegistry2_3.sol"; +import {ZKSyncAutomationRegistryLogicA2_3} from "../../v2_3_zksync/ZKSyncAutomationRegistryLogicA2_3.sol"; +import {ZKSyncAutomationRegistryLogicB2_3} from "../../v2_3_zksync/ZKSyncAutomationRegistryLogicB2_3.sol"; +import {ZKSyncAutomationRegistryLogicC2_3} from "../../v2_3_zksync/ZKSyncAutomationRegistryLogicC2_3.sol"; +import {ZKSyncAutomationRegistryBase2_3 as ZKSyncAutoBase} from "../../v2_3_zksync/ZKSyncAutomationRegistryBase2_3.sol"; +import {IAutomationRegistryMaster2_3 as Registry, AutomationRegistryBase2_3} from "../../interfaces/v2_3/IAutomationRegistryMaster2_3.sol"; +import {AutomationRegistrar2_3} from "../../v2_3/AutomationRegistrar2_3.sol"; +import {ChainModuleBase} from "../../chains/ChainModuleBase.sol"; +import {IERC20Metadata as IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {MockUpkeep} from "../../mocks/MockUpkeep.sol"; +import {IWrappedNative} from "../../interfaces/v2_3/IWrappedNative.sol"; +import {WETH9} from "../WETH9.sol"; +import {MockGasBoundCaller} from "../../../tests/MockGasBoundCaller.sol"; +import {MockZKSyncSystemContext} from "../../../tests/MockZKSyncSystemContext.sol"; + +/** + * @title BaseTest provides basic test setup procedures and dependencies for use by other + * unit tests + */ +contract BaseTest is Test { + // test state (not exposed to derived tests) + uint256 private nonce; + + // constants + address internal constant ZERO_ADDRESS = address(0); + uint32 internal constant DEFAULT_GAS_FEE_PPB = 10_000_000; + uint24 internal constant DEFAULT_FLAT_FEE_MILLI_CENTS = 2_000; + + // config + uint8 internal constant F = 1; // number of faulty nodes + uint64 internal constant OFFCHAIN_CONFIG_VERSION = 30; // 2 for OCR2 + + // contracts + LinkToken internal linkToken; + ERC20Mock6Decimals internal usdToken6; + ERC20Mock internal usdToken18; + ERC20Mock internal usdToken18_2; + WETH9 internal weth; + MockV3Aggregator internal LINK_USD_FEED; + MockV3Aggregator internal NATIVE_USD_FEED; + MockV3Aggregator internal USDTOKEN_USD_FEED; + MockV3Aggregator internal FAST_GAS_FEED; + MockUpkeep internal TARGET1; + MockUpkeep internal TARGET2; + Transcoder internal TRANSCODER; + MockGasBoundCaller internal GAS_BOUND_CALLER; + MockZKSyncSystemContext internal SYSTEM_CONTEXT; + + // roles + address internal constant OWNER = address(uint160(uint256(keccak256("OWNER")))); + address internal constant UPKEEP_ADMIN = address(uint160(uint256(keccak256("UPKEEP_ADMIN")))); + address internal constant FINANCE_ADMIN = address(uint160(uint256(keccak256("FINANCE_ADMIN")))); + address internal constant STRANGER = address(uint160(uint256(keccak256("STRANGER")))); + address internal constant BROKE_USER = address(uint160(uint256(keccak256("BROKE_USER")))); // do not mint to this address + address internal constant PRIVILEGE_MANAGER = address(uint160(uint256(keccak256("PRIVILEGE_MANAGER")))); + + // nodes + uint256 internal constant SIGNING_KEY0 = 0x7b2e97fe057e6de99d6872a2ef2abf52c9b4469bc848c2465ac3fcd8d336e81d; + uint256 internal constant SIGNING_KEY1 = 0xab56160806b05ef1796789248e1d7f34a6465c5280899159d645218cd216cee6; + uint256 internal constant SIGNING_KEY2 = 0x6ec7caa8406a49b76736602810e0a2871959fbbb675e23a8590839e4717f1f7f; + uint256 internal constant SIGNING_KEY3 = 0x80f14b11da94ae7f29d9a7713ea13dc838e31960a5c0f2baf45ed458947b730a; + address[] internal SIGNERS = new address[](4); + address[] internal TRANSMITTERS = new address[](4); + address[] internal NEW_TRANSMITTERS = new address[](4); + address[] internal PAYEES = new address[](4); + address[] internal NEW_PAYEES = new address[](4); + + function setUp() public virtual { + vm.startPrank(OWNER); + linkToken = new LinkToken(); + linkToken.grantMintRole(OWNER); + usdToken18 = new ERC20Mock("MOCK_ERC20_18Decimals", "MOCK_ERC20_18Decimals", OWNER, 0); + usdToken18_2 = new ERC20Mock("Second_MOCK_ERC20_18Decimals", "Second_MOCK_ERC20_18Decimals", OWNER, 0); + usdToken6 = new ERC20Mock6Decimals("MOCK_ERC20_6Decimals", "MOCK_ERC20_6Decimals", OWNER, 0); + weth = new WETH9(); + + LINK_USD_FEED = new MockV3Aggregator(8, 2_000_000_000); // $20 + NATIVE_USD_FEED = new MockV3Aggregator(8, 400_000_000_000); // $4,000 + USDTOKEN_USD_FEED = new MockV3Aggregator(8, 100_000_000); // $1 + FAST_GAS_FEED = new MockV3Aggregator(0, 1_000_000_000); // 1 gwei + + TARGET1 = new MockUpkeep(); + TARGET2 = new MockUpkeep(); + + TRANSCODER = new Transcoder(); + GAS_BOUND_CALLER = new MockGasBoundCaller(); + SYSTEM_CONTEXT = new MockZKSyncSystemContext(); + + bytes memory callerCode = address(GAS_BOUND_CALLER).code; + vm.etch(0xc706EC7dfA5D4Dc87f29f859094165E8290530f5, callerCode); + + bytes memory contextCode = address(SYSTEM_CONTEXT).code; + vm.etch(0x000000000000000000000000000000000000800B, contextCode); + + SIGNERS[0] = vm.addr(SIGNING_KEY0); //0xc110458BE52CaA6bB68E66969C3218A4D9Db0211 + SIGNERS[1] = vm.addr(SIGNING_KEY1); //0xc110a19c08f1da7F5FfB281dc93630923F8E3719 + SIGNERS[2] = vm.addr(SIGNING_KEY2); //0xc110fdF6e8fD679C7Cc11602d1cd829211A18e9b + SIGNERS[3] = vm.addr(SIGNING_KEY3); //0xc11028017c9b445B6bF8aE7da951B5cC28B326C0 + + TRANSMITTERS[0] = address(uint160(uint256(keccak256("TRANSMITTER1")))); + TRANSMITTERS[1] = address(uint160(uint256(keccak256("TRANSMITTER2")))); + TRANSMITTERS[2] = address(uint160(uint256(keccak256("TRANSMITTER3")))); + TRANSMITTERS[3] = address(uint160(uint256(keccak256("TRANSMITTER4")))); + NEW_TRANSMITTERS[0] = address(uint160(uint256(keccak256("TRANSMITTER1")))); + NEW_TRANSMITTERS[1] = address(uint160(uint256(keccak256("TRANSMITTER2")))); + NEW_TRANSMITTERS[2] = address(uint160(uint256(keccak256("TRANSMITTER5")))); + NEW_TRANSMITTERS[3] = address(uint160(uint256(keccak256("TRANSMITTER6")))); + + PAYEES[0] = address(100); + PAYEES[1] = address(101); + PAYEES[2] = address(102); + PAYEES[3] = address(103); + NEW_PAYEES[0] = address(100); + NEW_PAYEES[1] = address(101); + NEW_PAYEES[2] = address(106); + NEW_PAYEES[3] = address(107); + + // mint funds + vm.deal(OWNER, 100 ether); + vm.deal(UPKEEP_ADMIN, 100 ether); + vm.deal(FINANCE_ADMIN, 100 ether); + vm.deal(STRANGER, 100 ether); + + linkToken.mint(OWNER, 1000e18); + linkToken.mint(UPKEEP_ADMIN, 1000e18); + linkToken.mint(FINANCE_ADMIN, 1000e18); + linkToken.mint(STRANGER, 1000e18); + + usdToken18.mint(OWNER, 1000e18); + usdToken18.mint(UPKEEP_ADMIN, 1000e18); + usdToken18.mint(FINANCE_ADMIN, 1000e18); + usdToken18.mint(STRANGER, 1000e18); + + usdToken18_2.mint(UPKEEP_ADMIN, 1000e18); + + usdToken6.mint(OWNER, 1000e6); + usdToken6.mint(UPKEEP_ADMIN, 1000e6); + usdToken6.mint(FINANCE_ADMIN, 1000e6); + usdToken6.mint(STRANGER, 1000e6); + + weth.mint(OWNER, 1000e18); + weth.mint(UPKEEP_ADMIN, 1000e18); + weth.mint(FINANCE_ADMIN, 1000e18); + weth.mint(STRANGER, 1000e18); + + vm.stopPrank(); + } + + /// @notice deploys the component parts of a registry, but nothing more + function deployZKSyncRegistry(ZKSyncAutoBase.PayoutMode payoutMode) internal returns (Registry) { + AutomationForwarderLogic forwarderLogic = new AutomationForwarderLogic(); + ZKSyncAutomationRegistryLogicC2_3 logicC2_3 = new ZKSyncAutomationRegistryLogicC2_3( + address(linkToken), + address(LINK_USD_FEED), + address(NATIVE_USD_FEED), + address(FAST_GAS_FEED), + address(forwarderLogic), + ZERO_ADDRESS, + payoutMode, + address(weth) + ); + ZKSyncAutomationRegistryLogicB2_3 logicB2_3 = new ZKSyncAutomationRegistryLogicB2_3(logicC2_3); + ZKSyncAutomationRegistryLogicA2_3 logicA2_3 = new ZKSyncAutomationRegistryLogicA2_3(logicB2_3); + return Registry(payable(address(new ZKSyncAutomationRegistry2_3(logicA2_3)))); + } + + /// @notice deploys and configures a registry, registrar, and everything needed for most tests + function deployAndConfigureZKSyncRegistryAndRegistrar( + ZKSyncAutoBase.PayoutMode payoutMode + ) internal returns (Registry, AutomationRegistrar2_3) { + Registry registry = deployZKSyncRegistry(payoutMode); + + IERC20[] memory billingTokens = new IERC20[](4); + billingTokens[0] = IERC20(address(usdToken18)); + billingTokens[1] = IERC20(address(weth)); + billingTokens[2] = IERC20(address(linkToken)); + billingTokens[3] = IERC20(address(usdToken6)); + uint256[] memory minRegistrationFees = new uint256[](billingTokens.length); + minRegistrationFees[0] = 100e18; // 100 USD + minRegistrationFees[1] = 5e18; // 5 Native + minRegistrationFees[2] = 5e18; // 5 LINK + minRegistrationFees[3] = 100e6; // 100 USD + address[] memory billingTokenAddresses = new address[](billingTokens.length); + for (uint256 i = 0; i < billingTokens.length; i++) { + billingTokenAddresses[i] = address(billingTokens[i]); + } + AutomationRegistryBase2_3.BillingConfig[] + memory billingTokenConfigs = new AutomationRegistryBase2_3.BillingConfig[](billingTokens.length); + billingTokenConfigs[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: DEFAULT_GAS_FEE_PPB, // 15% + flatFeeMilliCents: DEFAULT_FLAT_FEE_MILLI_CENTS, // 2 cents + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 100_000_000, // $1 + minSpend: 1e18, // 1 USD + decimals: 18 + }); + billingTokenConfigs[1] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: DEFAULT_GAS_FEE_PPB, // 15% + flatFeeMilliCents: DEFAULT_FLAT_FEE_MILLI_CENTS, // 2 cents + priceFeed: address(NATIVE_USD_FEED), + fallbackPrice: 100_000_000, // $1 + minSpend: 5e18, // 5 Native + decimals: 18 + }); + billingTokenConfigs[2] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: DEFAULT_GAS_FEE_PPB, // 10% + flatFeeMilliCents: DEFAULT_FLAT_FEE_MILLI_CENTS, // 2 cents + priceFeed: address(LINK_USD_FEED), + fallbackPrice: 1_000_000_000, // $10 + minSpend: 1e18, // 1 LINK + decimals: 18 + }); + billingTokenConfigs[3] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: DEFAULT_GAS_FEE_PPB, // 15% + flatFeeMilliCents: DEFAULT_FLAT_FEE_MILLI_CENTS, // 2 cents + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 1e8, // $1 + minSpend: 1e6, // 1 USD + decimals: 6 + }); + + if (payoutMode == ZKSyncAutoBase.PayoutMode.OFF_CHAIN) { + // remove LINK as a payment method if we are settling offchain + assembly { + mstore(billingTokens, 2) + mstore(minRegistrationFees, 2) + mstore(billingTokenAddresses, 2) + mstore(billingTokenConfigs, 2) + } + } + + // deploy registrar + AutomationRegistrar2_3.InitialTriggerConfig[] + memory triggerConfigs = new AutomationRegistrar2_3.InitialTriggerConfig[](2); + triggerConfigs[0] = AutomationRegistrar2_3.InitialTriggerConfig({ + triggerType: 0, // condition + autoApproveType: AutomationRegistrar2_3.AutoApproveType.DISABLED, + autoApproveMaxAllowed: 0 + }); + triggerConfigs[1] = AutomationRegistrar2_3.InitialTriggerConfig({ + triggerType: 1, // log + autoApproveType: AutomationRegistrar2_3.AutoApproveType.DISABLED, + autoApproveMaxAllowed: 0 + }); + AutomationRegistrar2_3 registrar = new AutomationRegistrar2_3( + address(linkToken), + registry, + triggerConfigs, + billingTokens, + minRegistrationFees, + IWrappedNative(address(weth)) + ); + + address[] memory registrars; + registrars = new address[](1); + registrars[0] = address(registrar); + + AutomationRegistryBase2_3.OnchainConfig memory cfg = AutomationRegistryBase2_3.OnchainConfig({ + checkGasLimit: 5_000_000, + stalenessSeconds: 90_000, + gasCeilingMultiplier: 2, + maxPerformGas: 10_000_000, + maxCheckDataSize: 5_000, + maxPerformDataSize: 5_000, + maxRevertDataSize: 5_000, + fallbackGasPrice: 20_000_000_000, + fallbackLinkPrice: 2_000_000_000, // $20 + fallbackNativePrice: 400_000_000_000, // $4,000 + transcoder: address(TRANSCODER), + registrars: registrars, + upkeepPrivilegeManager: PRIVILEGE_MANAGER, + chainModule: address(new ChainModuleBase()), + reorgProtectionEnabled: true, + financeAdmin: FINANCE_ADMIN + }); + + registry.setConfigTypeSafe( + SIGNERS, + TRANSMITTERS, + F, + cfg, + OFFCHAIN_CONFIG_VERSION, + "", + billingTokenAddresses, + billingTokenConfigs + ); + return (registry, registrar); + } + + /// @notice this function updates the billing config for the provided token on the provided registry, + /// and throws an error if the token is not found + function _updateBillingTokenConfig( + Registry registry, + address billingToken, + AutomationRegistryBase2_3.BillingConfig memory newConfig + ) internal { + (, , address[] memory signers, address[] memory transmitters, uint8 f) = registry.getState(); + AutomationRegistryBase2_3.OnchainConfig memory config = registry.getConfig(); + address[] memory billingTokens = registry.getBillingTokens(); + + AutomationRegistryBase2_3.BillingConfig[] + memory billingTokenConfigs = new AutomationRegistryBase2_3.BillingConfig[](billingTokens.length); + + bool found = false; + for (uint256 i = 0; i < billingTokens.length; i++) { + if (billingTokens[i] == billingToken) { + found = true; + billingTokenConfigs[i] = newConfig; + } else { + billingTokenConfigs[i] = registry.getBillingTokenConfig(billingTokens[i]); + } + } + require(found, "could not find billing token provided on registry"); + + registry.setConfigTypeSafe( + signers, + transmitters, + f, + config, + OFFCHAIN_CONFIG_VERSION, + "", + billingTokens, + billingTokenConfigs + ); + } + + /// @notice this function removes a billing token from the registry + function _removeBillingTokenConfig(Registry registry, address billingToken) internal { + (, , address[] memory signers, address[] memory transmitters, uint8 f) = registry.getState(); + AutomationRegistryBase2_3.OnchainConfig memory config = registry.getConfig(); + address[] memory billingTokens = registry.getBillingTokens(); + + address[] memory newBillingTokens = new address[](billingTokens.length - 1); + AutomationRegistryBase2_3.BillingConfig[] + memory billingTokenConfigs = new AutomationRegistryBase2_3.BillingConfig[](billingTokens.length - 1); + + uint256 j = 0; + for (uint256 i = 0; i < billingTokens.length; i++) { + if (billingTokens[i] != billingToken) { + if (j == newBillingTokens.length) revert("could not find billing token provided on registry"); + newBillingTokens[j] = billingTokens[i]; + billingTokenConfigs[j] = registry.getBillingTokenConfig(billingTokens[i]); + j++; + } + } + + registry.setConfigTypeSafe( + signers, + transmitters, + f, + config, + OFFCHAIN_CONFIG_VERSION, + "", + newBillingTokens, + billingTokenConfigs + ); + } + + function _transmit(uint256 id, Registry registry, bytes4 selector) internal { + uint256[] memory ids = new uint256[](1); + ids[0] = id; + _transmit(ids, registry, selector); + } + + function _transmit(uint256[] memory ids, Registry registry, bytes4 selector) internal { + bytes memory reportBytes; + { + uint256[] memory upkeepIds = new uint256[](ids.length); + uint256[] memory gasLimits = new uint256[](ids.length); + bytes[] memory performDatas = new bytes[](ids.length); + bytes[] memory triggers = new bytes[](ids.length); + for (uint256 i = 0; i < ids.length; i++) { + upkeepIds[i] = ids[i]; + gasLimits[i] = registry.getUpkeep(ids[i]).performGas; + performDatas[i] = new bytes(0); + uint8 triggerType = registry.getTriggerType(ids[i]); + if (triggerType == 0) { + triggers[i] = _encodeConditionalTrigger( + ZKSyncAutoBase.ConditionalTrigger(uint32(block.number - 1), blockhash(block.number - 1)) + ); + } else { + revert("not implemented"); + } + } + ZKSyncAutoBase.Report memory report = ZKSyncAutoBase.Report( + uint256(1000000000), + uint256(2000000000), + upkeepIds, + gasLimits, + triggers, + performDatas + ); + + reportBytes = _encodeReport(report); + } + (, , bytes32 configDigest) = registry.latestConfigDetails(); + bytes32[3] memory reportContext = [configDigest, configDigest, configDigest]; + uint256[] memory signerPKs = new uint256[](2); + signerPKs[0] = SIGNING_KEY0; + signerPKs[1] = SIGNING_KEY1; + (bytes32[] memory rs, bytes32[] memory ss, bytes32 vs) = _signReport(reportBytes, reportContext, signerPKs); + + vm.startPrank(TRANSMITTERS[0]); + if (selector != bytes4(0)) { + vm.expectRevert(selector); + } + registry.transmit(reportContext, reportBytes, rs, ss, vs); + vm.stopPrank(); + } + + /// @notice Gather signatures on report data + /// @param report - Report bytes generated from `_buildReport` + /// @param reportContext - Report context bytes32 generated from `_buildReport` + /// @param signerPrivateKeys - One or more addresses that will sign the report data + /// @return rawRs - Signature rs + /// @return rawSs - Signature ss + /// @return rawVs - Signature vs + function _signReport( + bytes memory report, + bytes32[3] memory reportContext, + uint256[] memory signerPrivateKeys + ) internal pure returns (bytes32[] memory, bytes32[] memory, bytes32) { + bytes32[] memory rs = new bytes32[](signerPrivateKeys.length); + bytes32[] memory ss = new bytes32[](signerPrivateKeys.length); + bytes memory vs = new bytes(signerPrivateKeys.length); + + bytes32 reportDigest = keccak256(abi.encodePacked(keccak256(report), reportContext)); + + for (uint256 i = 0; i < signerPrivateKeys.length; i++) { + (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKeys[i], reportDigest); + rs[i] = r; + ss[i] = s; + vs[i] = bytes1(v - 27); + } + + return (rs, ss, bytes32(vs)); + } + + function _encodeReport(ZKSyncAutoBase.Report memory report) internal pure returns (bytes memory reportBytes) { + return abi.encode(report); + } + + function _encodeConditionalTrigger( + ZKSyncAutoBase.ConditionalTrigger memory trigger + ) internal pure returns (bytes memory triggerBytes) { + return abi.encode(trigger.blockNum, trigger.blockHash); + } + + /// @dev mints LINK to the recipient + function _mintLink(address recipient, uint256 amount) internal { + vm.prank(OWNER); + linkToken.mint(recipient, amount); + } + + /// @dev mints USDToken with 18 decimals to the recipient + function _mintERC20_18Decimals(address recipient, uint256 amount) internal { + vm.prank(OWNER); + usdToken18.mint(recipient, amount); + } + + /// @dev returns a pseudo-random 32 bytes + function _random() private returns (bytes32) { + nonce++; + return keccak256(abi.encode(block.timestamp, nonce)); + } + + /// @dev returns a pseudo-random number + function randomNumber() internal returns (uint256) { + return uint256(_random()); + } + + /// @dev returns a pseudo-random address + function randomAddress() internal returns (address) { + return address(uint160(randomNumber())); + } + + /// @dev returns a pseudo-random byte array + function randomBytes(uint256 length) internal returns (bytes memory) { + bytes memory result = new bytes(length); + bytes32 entropy; + for (uint256 i = 0; i < length; i++) { + if (i % 32 == 0) { + entropy = _random(); + } + result[i] = entropy[i % 32]; + } + return result; + } +} diff --git a/contracts/src/v0.8/automation/test/v2_3_zksync/ZKSyncAutomationRegistry2_3.t.sol b/contracts/src/v0.8/automation/test/v2_3_zksync/ZKSyncAutomationRegistry2_3.t.sol new file mode 100644 index 00000000000..7098d9f38fa --- /dev/null +++ b/contracts/src/v0.8/automation/test/v2_3_zksync/ZKSyncAutomationRegistry2_3.t.sol @@ -0,0 +1,2772 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {Vm} from "forge-std/Test.sol"; +import {BaseTest} from "./BaseTest.t.sol"; +import {ZKSyncAutomationRegistryBase2_3 as AutoBase} from "../../v2_3_zksync/ZKSyncAutomationRegistryBase2_3.sol"; +import {AutomationRegistrar2_3 as Registrar} from "../../v2_3/AutomationRegistrar2_3.sol"; +import {IAutomationRegistryMaster2_3 as Registry, AutomationRegistryBase2_3, IAutomationV21PlusCommon} from "../../interfaces/v2_3/IAutomationRegistryMaster2_3.sol"; +import {ChainModuleBase} from "../../chains/ChainModuleBase.sol"; +import {IERC20Metadata as IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import {IWrappedNative} from "../../interfaces/v2_3/IWrappedNative.sol"; + +// forge test --match-path src/v0.8/automation/test/v2_3_zksync/ZKSyncAutomationRegistry2_3.t.sol --match-test + +enum Trigger { + CONDITION, + LOG +} + +contract SetUp is BaseTest { + Registry internal registry; + AutomationRegistryBase2_3.OnchainConfig internal config; + bytes internal constant offchainConfigBytes = abi.encode(1234, ZERO_ADDRESS); + + uint256 linkUpkeepID; + uint256 linkUpkeepID2; // 2 upkeeps use the same billing token (LINK) to test migration scenario + uint256 usdUpkeepID18; // 1 upkeep uses ERC20 token with 18 decimals + uint256 usdUpkeepID6; // 1 upkeep uses ERC20 token with 6 decimals + uint256 nativeUpkeepID; + + function setUp() public virtual override { + super.setUp(); + (registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN); + config = registry.getConfig(); + + vm.startPrank(OWNER); + linkToken.approve(address(registry), type(uint256).max); + usdToken6.approve(address(registry), type(uint256).max); + usdToken18.approve(address(registry), type(uint256).max); + weth.approve(address(registry), type(uint256).max); + vm.startPrank(UPKEEP_ADMIN); + linkToken.approve(address(registry), type(uint256).max); + usdToken6.approve(address(registry), type(uint256).max); + usdToken18.approve(address(registry), type(uint256).max); + weth.approve(address(registry), type(uint256).max); + vm.startPrank(STRANGER); + linkToken.approve(address(registry), type(uint256).max); + usdToken6.approve(address(registry), type(uint256).max); + usdToken18.approve(address(registry), type(uint256).max); + weth.approve(address(registry), type(uint256).max); + vm.stopPrank(); + + linkUpkeepID = registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + "", + "", + "" + ); + + linkUpkeepID2 = registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + "", + "", + "" + ); + + usdUpkeepID18 = registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(usdToken18), + "", + "", + "" + ); + + usdUpkeepID6 = registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(usdToken6), + "", + "", + "" + ); + + nativeUpkeepID = registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(weth), + "", + "", + "" + ); + + vm.startPrank(OWNER); + registry.addFunds(linkUpkeepID, registry.getMinBalanceForUpkeep(linkUpkeepID)); + registry.addFunds(linkUpkeepID2, registry.getMinBalanceForUpkeep(linkUpkeepID2)); + registry.addFunds(usdUpkeepID18, registry.getMinBalanceForUpkeep(usdUpkeepID18)); + registry.addFunds(usdUpkeepID6, registry.getMinBalanceForUpkeep(usdUpkeepID6)); + registry.addFunds(nativeUpkeepID, registry.getMinBalanceForUpkeep(nativeUpkeepID)); + vm.stopPrank(); + } +} + +contract LatestConfigDetails is SetUp { + function testGet() public { + (uint32 configCount, uint32 blockNumber, bytes32 configDigest) = registry.latestConfigDetails(); + assertEq(configCount, 1); + assertTrue(blockNumber > 0); + assertNotEq(configDigest, ""); + } +} + +contract CheckUpkeep is SetUp { + function testPreventExecutionOnCheckUpkeep() public { + uint256 id = 1; + bytes memory triggerData = abi.encodePacked("trigger_data"); + + // The tx.origin is the DEFAULT_SENDER (0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38) of foundry + // Expecting a revert since the tx.origin is not address(0) + vm.expectRevert(abi.encodeWithSelector(Registry.OnlySimulatedBackend.selector)); + registry.checkUpkeep(id, triggerData); + } +} + +contract WithdrawFunds is SetUp { + event FundsWithdrawn(uint256 indexed id, uint256 amount, address to); + + function test_RevertsWhen_CalledByNonAdmin() external { + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + vm.prank(STRANGER); + registry.withdrawFunds(linkUpkeepID, STRANGER); + } + + function test_RevertsWhen_InvalidRecipient() external { + vm.expectRevert(Registry.InvalidRecipient.selector); + vm.prank(UPKEEP_ADMIN); + registry.withdrawFunds(linkUpkeepID, ZERO_ADDRESS); + } + + function test_RevertsWhen_UpkeepNotCanceled() external { + vm.expectRevert(Registry.UpkeepNotCanceled.selector); + vm.prank(UPKEEP_ADMIN); + registry.withdrawFunds(linkUpkeepID, UPKEEP_ADMIN); + } + + function test_Happy_Link() external { + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + vm.roll(100 + block.number); + + uint256 startUpkeepAdminBalance = linkToken.balanceOf(UPKEEP_ADMIN); + uint256 startLinkReserveAmountBalance = registry.getReserveAmount(address(linkToken)); + + uint256 upkeepBalance = registry.getBalance(linkUpkeepID); + vm.expectEmit(); + emit FundsWithdrawn(linkUpkeepID, upkeepBalance, address(UPKEEP_ADMIN)); + registry.withdrawFunds(linkUpkeepID, UPKEEP_ADMIN); + + assertEq(registry.getBalance(linkUpkeepID), 0); + assertEq(linkToken.balanceOf(UPKEEP_ADMIN), startUpkeepAdminBalance + upkeepBalance); + assertEq(registry.getReserveAmount(address(linkToken)), startLinkReserveAmountBalance - upkeepBalance); + } + + function test_Happy_USDToken() external { + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(usdUpkeepID6); + vm.roll(100 + block.number); + + uint256 startUpkeepAdminBalance = usdToken6.balanceOf(UPKEEP_ADMIN); + uint256 startUSDToken6ReserveAmountBalance = registry.getReserveAmount(address(usdToken6)); + + uint256 upkeepBalance = registry.getBalance(usdUpkeepID6); + vm.expectEmit(); + emit FundsWithdrawn(usdUpkeepID6, upkeepBalance, address(UPKEEP_ADMIN)); + registry.withdrawFunds(usdUpkeepID6, UPKEEP_ADMIN); + + assertEq(registry.getBalance(usdUpkeepID6), 0); + assertEq(usdToken6.balanceOf(UPKEEP_ADMIN), startUpkeepAdminBalance + upkeepBalance); + assertEq(registry.getReserveAmount(address(usdToken6)), startUSDToken6ReserveAmountBalance - upkeepBalance); + } +} + +contract AddFunds is SetUp { + event FundsAdded(uint256 indexed id, address indexed from, uint96 amount); + + // when msg.value is 0, it uses the ERC20 payment path + function test_HappyWhen_NativeUpkeep_WithMsgValue0() external { + vm.startPrank(OWNER); + uint256 startRegistryBalance = registry.getBalance(nativeUpkeepID); + uint256 startTokenBalance = registry.getBalance(nativeUpkeepID); + registry.addFunds(nativeUpkeepID, 1); + assertEq(registry.getBalance(nativeUpkeepID), startRegistryBalance + 1); + assertEq(weth.balanceOf(address(registry)), startTokenBalance + 1); + assertEq(registry.getAvailableERC20ForPayment(address(weth)), 0); + } + + // when msg.value is not 0, it uses the native payment path + function test_HappyWhen_NativeUpkeep_WithMsgValueNot0() external { + uint256 startRegistryBalance = registry.getBalance(nativeUpkeepID); + uint256 startTokenBalance = registry.getBalance(nativeUpkeepID); + registry.addFunds{value: 1}(nativeUpkeepID, 1000); // parameter amount should be ignored + assertEq(registry.getBalance(nativeUpkeepID), startRegistryBalance + 1); + assertEq(weth.balanceOf(address(registry)), startTokenBalance + 1); + assertEq(registry.getAvailableERC20ForPayment(address(weth)), 0); + } + + // it fails when the billing token is not native, but trying to pay with native + function test_RevertsWhen_NativePaymentDoesntMatchBillingToken() external { + vm.expectRevert(abi.encodeWithSelector(Registry.InvalidToken.selector)); + registry.addFunds{value: 1}(linkUpkeepID, 0); + } + + function test_RevertsWhen_UpkeepDoesNotExist() public { + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.addFunds(randomNumber(), 1); + } + + function test_RevertsWhen_UpkeepIsCanceled() public { + registry.cancelUpkeep(linkUpkeepID); + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.addFunds(linkUpkeepID, 1); + } + + function test_anyoneCanAddFunds() public { + uint256 startAmount = registry.getBalance(linkUpkeepID); + vm.prank(UPKEEP_ADMIN); + registry.addFunds(linkUpkeepID, 1); + assertEq(registry.getBalance(linkUpkeepID), startAmount + 1); + vm.prank(STRANGER); + registry.addFunds(linkUpkeepID, 1); + assertEq(registry.getBalance(linkUpkeepID), startAmount + 2); + } + + function test_movesFundFromCorrectToken() public { + vm.startPrank(UPKEEP_ADMIN); + + uint256 startLINKRegistryBalance = linkToken.balanceOf(address(registry)); + uint256 startUSDRegistryBalance = usdToken18.balanceOf(address(registry)); + uint256 startLinkUpkeepBalance = registry.getBalance(linkUpkeepID); + uint256 startUSDUpkeepBalance = registry.getBalance(usdUpkeepID18); + + registry.addFunds(linkUpkeepID, 1); + assertEq(registry.getBalance(linkUpkeepID), startLinkUpkeepBalance + 1); + assertEq(registry.getBalance(usdUpkeepID18), startUSDRegistryBalance); + assertEq(linkToken.balanceOf(address(registry)), startLINKRegistryBalance + 1); + assertEq(usdToken18.balanceOf(address(registry)), startUSDUpkeepBalance); + + registry.addFunds(usdUpkeepID18, 2); + assertEq(registry.getBalance(linkUpkeepID), startLinkUpkeepBalance + 1); + assertEq(registry.getBalance(usdUpkeepID18), startUSDRegistryBalance + 2); + assertEq(linkToken.balanceOf(address(registry)), startLINKRegistryBalance + 1); + assertEq(usdToken18.balanceOf(address(registry)), startUSDUpkeepBalance + 2); + } + + function test_emitsAnEvent() public { + vm.startPrank(UPKEEP_ADMIN); + vm.expectEmit(); + emit FundsAdded(linkUpkeepID, address(UPKEEP_ADMIN), 100); + registry.addFunds(linkUpkeepID, 100); + } +} + +contract Withdraw is SetUp { + address internal aMockAddress = randomAddress(); + + function testLinkAvailableForPaymentReturnsLinkBalance() public { + uint256 startBalance = linkToken.balanceOf(address(registry)); + int256 startLinkAvailable = registry.linkAvailableForPayment(); + + //simulate a deposit of link to the liquidity pool + _mintLink(address(registry), 1e10); + + //check there's a balance + assertEq(linkToken.balanceOf(address(registry)), startBalance + 1e10); + + //check the link available has increased by the same amount + assertEq(uint256(registry.linkAvailableForPayment()), uint256(startLinkAvailable) + 1e10); + } + + function testWithdrawLinkRevertsBecauseOnlyFinanceAdminAllowed() public { + vm.expectRevert(abi.encodeWithSelector(Registry.OnlyFinanceAdmin.selector)); + registry.withdrawLink(aMockAddress, 1); + } + + function testWithdrawLinkRevertsBecauseOfInsufficientBalance() public { + vm.startPrank(FINANCE_ADMIN); + + // try to withdraw 1 link while there is 0 balance + vm.expectRevert(abi.encodeWithSelector(Registry.InsufficientBalance.selector, 0, 1)); + registry.withdrawLink(aMockAddress, 1); + + vm.stopPrank(); + } + + function testWithdrawLinkRevertsBecauseOfInvalidRecipient() public { + vm.startPrank(FINANCE_ADMIN); + + // try to withdraw 1 link while there is 0 balance + vm.expectRevert(abi.encodeWithSelector(Registry.InvalidRecipient.selector)); + registry.withdrawLink(ZERO_ADDRESS, 1); + + vm.stopPrank(); + } + + function testWithdrawLinkSuccess() public { + //simulate a deposit of link to the liquidity pool + _mintLink(address(registry), 1e10); + uint256 startBalance = linkToken.balanceOf(address(registry)); + + vm.startPrank(FINANCE_ADMIN); + + // try to withdraw 1 link while there is a ton of link available + registry.withdrawLink(aMockAddress, 1); + + vm.stopPrank(); + + assertEq(linkToken.balanceOf(address(aMockAddress)), 1); + assertEq(linkToken.balanceOf(address(registry)), startBalance - 1); + } + + function test_WithdrawERC20Fees_RespectsReserveAmount() public { + assertEq(registry.getBalance(usdUpkeepID18), registry.getReserveAmount(address(usdToken18))); + vm.startPrank(FINANCE_ADMIN); + vm.expectRevert(abi.encodeWithSelector(Registry.InsufficientBalance.selector, 0, 1)); + registry.withdrawERC20Fees(address(usdToken18), FINANCE_ADMIN, 1); + } + + function test_WithdrawERC20Fees_RevertsWhen_AttemptingToWithdrawLINK() public { + _mintLink(address(registry), 1e10); + vm.startPrank(FINANCE_ADMIN); + vm.expectRevert(Registry.InvalidToken.selector); + registry.withdrawERC20Fees(address(linkToken), FINANCE_ADMIN, 1); // should revert + registry.withdrawLink(FINANCE_ADMIN, 1); // but using link withdraw functions succeeds + } + + // default is ON_CHAIN mode + function test_WithdrawERC20Fees_RevertsWhen_LinkAvailableForPaymentIsNegative() public { + _transmit(usdUpkeepID18, registry, bytes4(0)); // adds USD token to finance withdrawable, and gives NOPs a LINK balance + require(registry.linkAvailableForPayment() < 0, "linkAvailableForPayment should be negative"); + require( + registry.getAvailableERC20ForPayment(address(usdToken18)) > 0, + "ERC20AvailableForPayment should be positive" + ); + vm.expectRevert(Registry.InsufficientLinkLiquidity.selector); + vm.prank(FINANCE_ADMIN); + registry.withdrawERC20Fees(address(usdToken18), FINANCE_ADMIN, 1); // should revert + _mintLink(address(registry), uint256(registry.linkAvailableForPayment() * -10)); // top up LINK liquidity pool + vm.prank(FINANCE_ADMIN); + registry.withdrawERC20Fees(address(usdToken18), FINANCE_ADMIN, 1); // now finance can withdraw + } + + function test_WithdrawERC20Fees_InOffChainMode_Happy() public { + // deploy and configure a registry with OFF_CHAIN payout + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + + // register an upkeep and add funds + uint256 id = registry.registerUpkeep(address(TARGET1), 1000000, UPKEEP_ADMIN, 0, address(usdToken18), "", "", ""); + _mintERC20_18Decimals(UPKEEP_ADMIN, 1e20); + vm.startPrank(UPKEEP_ADMIN); + usdToken18.approve(address(registry), 1e20); + registry.addFunds(id, 1e20); + + // manually create a transmit so transmitters earn some rewards + _transmit(id, registry, bytes4(0)); + require(registry.linkAvailableForPayment() < 0, "linkAvailableForPayment should be negative"); + vm.prank(FINANCE_ADMIN); + registry.withdrawERC20Fees(address(usdToken18), aMockAddress, 1); // finance can withdraw + + // recipient should get the funds + assertEq(usdToken18.balanceOf(address(aMockAddress)), 1); + } + + function testWithdrawERC20FeeSuccess() public { + // deposit excess USDToken to the registry (this goes to the "finance withdrawable" pool be default) + uint256 startReserveAmount = registry.getReserveAmount(address(usdToken18)); + uint256 startAmount = usdToken18.balanceOf(address(registry)); + _mintERC20_18Decimals(address(registry), 1e10); + + // depositing shouldn't change reserve amount + assertEq(registry.getReserveAmount(address(usdToken18)), startReserveAmount); + + vm.startPrank(FINANCE_ADMIN); + + // try to withdraw 1 USDToken + registry.withdrawERC20Fees(address(usdToken18), aMockAddress, 1); + + vm.stopPrank(); + + assertEq(usdToken18.balanceOf(address(aMockAddress)), 1); + assertEq(usdToken18.balanceOf(address(registry)), startAmount + 1e10 - 1); + assertEq(registry.getReserveAmount(address(usdToken18)), startReserveAmount); + } +} + +contract SetConfig is SetUp { + event ConfigSet( + uint32 previousConfigBlockNumber, + bytes32 configDigest, + uint64 configCount, + address[] signers, + address[] transmitters, + uint8 f, + bytes onchainConfig, + uint64 offchainConfigVersion, + bytes offchainConfig + ); + + address module = address(new ChainModuleBase()); + + AutomationRegistryBase2_3.OnchainConfig cfg = + AutomationRegistryBase2_3.OnchainConfig({ + checkGasLimit: 5_000_000, + stalenessSeconds: 90_000, + gasCeilingMultiplier: 0, + maxPerformGas: 10_000_000, + maxCheckDataSize: 5_000, + maxPerformDataSize: 5_000, + maxRevertDataSize: 5_000, + fallbackGasPrice: 20_000_000_000, + fallbackLinkPrice: 2_000_000_000, // $20 + fallbackNativePrice: 400_000_000_000, // $4,000 + transcoder: 0xB1e66855FD67f6e85F0f0fA38cd6fBABdf00923c, + registrars: _getRegistrars(), + upkeepPrivilegeManager: PRIVILEGE_MANAGER, + chainModule: module, + reorgProtectionEnabled: true, + financeAdmin: FINANCE_ADMIN + }); + + function testSetConfigSuccess() public { + (uint32 configCount, uint32 blockNumber, ) = registry.latestConfigDetails(); + assertEq(configCount, 1); + + address billingTokenAddress = address(usdToken18); + address[] memory billingTokens = new address[](1); + billingTokens[0] = billingTokenAddress; + + AutomationRegistryBase2_3.BillingConfig[] memory billingConfigs = new AutomationRegistryBase2_3.BillingConfig[](1); + billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_000, + flatFeeMilliCents: 20_000, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 2_000_000_000, // $20 + minSpend: 100_000, + decimals: 18 + }); + + bytes memory onchainConfigBytes = abi.encode(cfg); + bytes memory onchainConfigBytesWithBilling = abi.encode(cfg, billingTokens, billingConfigs); + + bytes32 configDigest = _configDigestFromConfigData( + block.chainid, + address(registry), + ++configCount, + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytes, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + vm.expectEmit(); + emit ConfigSet( + blockNumber, + configDigest, + configCount, + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytes, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + registry.setConfig( + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytesWithBilling, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + (, , address[] memory signers, address[] memory transmitters, uint8 f) = registry.getState(); + + assertEq(signers, SIGNERS); + assertEq(transmitters, TRANSMITTERS); + assertEq(f, F); + + AutomationRegistryBase2_3.BillingConfig memory config = registry.getBillingTokenConfig(billingTokenAddress); + assertEq(config.gasFeePPB, 5_000); + assertEq(config.flatFeeMilliCents, 20_000); + assertEq(config.priceFeed, address(USDTOKEN_USD_FEED)); + assertEq(config.minSpend, 100_000); + + address[] memory tokens = registry.getBillingTokens(); + assertEq(tokens.length, 1); + } + + function testSetConfigMultipleBillingConfigsSuccess() public { + (uint32 configCount, , ) = registry.latestConfigDetails(); + assertEq(configCount, 1); + + address billingTokenAddress1 = address(linkToken); + address billingTokenAddress2 = address(usdToken18); + address[] memory billingTokens = new address[](2); + billingTokens[0] = billingTokenAddress1; + billingTokens[1] = billingTokenAddress2; + + AutomationRegistryBase2_3.BillingConfig[] memory billingConfigs = new AutomationRegistryBase2_3.BillingConfig[](2); + billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_001, + flatFeeMilliCents: 20_001, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 100, + minSpend: 100, + decimals: 18 + }); + billingConfigs[1] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_002, + flatFeeMilliCents: 20_002, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 200, + minSpend: 200, + decimals: 18 + }); + + bytes memory onchainConfigBytesWithBilling = abi.encode(cfg, billingTokens, billingConfigs); + + registry.setConfig( + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytesWithBilling, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + (, , address[] memory signers, address[] memory transmitters, uint8 f) = registry.getState(); + + assertEq(signers, SIGNERS); + assertEq(transmitters, TRANSMITTERS); + assertEq(f, F); + + AutomationRegistryBase2_3.BillingConfig memory config1 = registry.getBillingTokenConfig(billingTokenAddress1); + assertEq(config1.gasFeePPB, 5_001); + assertEq(config1.flatFeeMilliCents, 20_001); + assertEq(config1.priceFeed, address(USDTOKEN_USD_FEED)); + assertEq(config1.fallbackPrice, 100); + assertEq(config1.minSpend, 100); + + AutomationRegistryBase2_3.BillingConfig memory config2 = registry.getBillingTokenConfig(billingTokenAddress2); + assertEq(config2.gasFeePPB, 5_002); + assertEq(config2.flatFeeMilliCents, 20_002); + assertEq(config2.priceFeed, address(USDTOKEN_USD_FEED)); + assertEq(config2.fallbackPrice, 200); + assertEq(config2.minSpend, 200); + + address[] memory tokens = registry.getBillingTokens(); + assertEq(tokens.length, 2); + } + + function testSetConfigTwiceAndLastSetOverwrites() public { + (uint32 configCount, , ) = registry.latestConfigDetails(); + assertEq(configCount, 1); + + // BillingConfig1 + address billingTokenAddress1 = address(usdToken18); + address[] memory billingTokens1 = new address[](1); + billingTokens1[0] = billingTokenAddress1; + + AutomationRegistryBase2_3.BillingConfig[] memory billingConfigs1 = new AutomationRegistryBase2_3.BillingConfig[](1); + billingConfigs1[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_001, + flatFeeMilliCents: 20_001, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 100, + minSpend: 100, + decimals: 18 + }); + + // the first time uses the default onchain config with 2 registrars + bytes memory onchainConfigBytesWithBilling1 = abi.encode(cfg, billingTokens1, billingConfigs1); + + // set config once + registry.setConfig( + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytesWithBilling1, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + (, IAutomationV21PlusCommon.OnchainConfigLegacy memory onchainConfig1, , , ) = registry.getState(); + assertEq(onchainConfig1.registrars.length, 2); + + // BillingConfig2 + address billingTokenAddress2 = address(usdToken18); + address[] memory billingTokens2 = new address[](1); + billingTokens2[0] = billingTokenAddress2; + + AutomationRegistryBase2_3.BillingConfig[] memory billingConfigs2 = new AutomationRegistryBase2_3.BillingConfig[](1); + billingConfigs2[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_002, + flatFeeMilliCents: 20_002, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 200, + minSpend: 200, + decimals: 18 + }); + + address[] memory newRegistrars = new address[](3); + newRegistrars[0] = address(uint160(uint256(keccak256("newRegistrar1")))); + newRegistrars[1] = address(uint160(uint256(keccak256("newRegistrar2")))); + newRegistrars[2] = address(uint160(uint256(keccak256("newRegistrar3")))); + + // new onchain config with 3 new registrars, all other fields stay the same as the default + AutomationRegistryBase2_3.OnchainConfig memory cfg2 = AutomationRegistryBase2_3.OnchainConfig({ + checkGasLimit: 5_000_000, + stalenessSeconds: 90_000, + gasCeilingMultiplier: 0, + maxPerformGas: 10_000_000, + maxCheckDataSize: 5_000, + maxPerformDataSize: 5_000, + maxRevertDataSize: 5_000, + fallbackGasPrice: 20_000_000_000, + fallbackLinkPrice: 2_000_000_000, // $20 + fallbackNativePrice: 400_000_000_000, // $4,000 + transcoder: 0xB1e66855FD67f6e85F0f0fA38cd6fBABdf00923c, + registrars: newRegistrars, + upkeepPrivilegeManager: PRIVILEGE_MANAGER, + chainModule: module, + reorgProtectionEnabled: true, + financeAdmin: FINANCE_ADMIN + }); + + // the second time uses the new onchain config with 3 new registrars and also new billing tokens/configs + bytes memory onchainConfigBytesWithBilling2 = abi.encode(cfg2, billingTokens2, billingConfigs2); + + // set config twice + registry.setConfig( + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytesWithBilling2, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + ( + , + IAutomationV21PlusCommon.OnchainConfigLegacy memory onchainConfig2, + address[] memory signers, + address[] memory transmitters, + uint8 f + ) = registry.getState(); + + assertEq(onchainConfig2.registrars.length, 3); + for (uint256 i = 0; i < newRegistrars.length; i++) { + assertEq(newRegistrars[i], onchainConfig2.registrars[i]); + } + assertEq(signers, SIGNERS); + assertEq(transmitters, TRANSMITTERS); + assertEq(f, F); + + AutomationRegistryBase2_3.BillingConfig memory config2 = registry.getBillingTokenConfig(billingTokenAddress2); + assertEq(config2.gasFeePPB, 5_002); + assertEq(config2.flatFeeMilliCents, 20_002); + assertEq(config2.priceFeed, address(USDTOKEN_USD_FEED)); + assertEq(config2.fallbackPrice, 200); + assertEq(config2.minSpend, 200); + + address[] memory tokens = registry.getBillingTokens(); + assertEq(tokens.length, 1); + } + + function testSetConfigDuplicateBillingConfigFailure() public { + (uint32 configCount, , ) = registry.latestConfigDetails(); + assertEq(configCount, 1); + + address billingTokenAddress1 = address(linkToken); + address billingTokenAddress2 = address(linkToken); + address[] memory billingTokens = new address[](2); + billingTokens[0] = billingTokenAddress1; + billingTokens[1] = billingTokenAddress2; + + AutomationRegistryBase2_3.BillingConfig[] memory billingConfigs = new AutomationRegistryBase2_3.BillingConfig[](2); + billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_001, + flatFeeMilliCents: 20_001, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 100, + minSpend: 100, + decimals: 18 + }); + billingConfigs[1] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_002, + flatFeeMilliCents: 20_002, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 200, + minSpend: 200, + decimals: 18 + }); + + bytes memory onchainConfigBytesWithBilling = abi.encode(cfg, billingTokens, billingConfigs); + + // expect revert because of duplicate tokens + vm.expectRevert(abi.encodeWithSelector(Registry.DuplicateEntry.selector)); + registry.setConfig( + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytesWithBilling, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + } + + function testSetConfigRevertDueToInvalidToken() public { + address[] memory billingTokens = new address[](1); + billingTokens[0] = address(linkToken); + + AutomationRegistryBase2_3.BillingConfig[] memory billingConfigs = new AutomationRegistryBase2_3.BillingConfig[](1); + billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_000, + flatFeeMilliCents: 20_000, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 2_000_000_000, // $20 + minSpend: 100_000, + decimals: 18 + }); + + // deploy registry with OFF_CHAIN payout mode + registry = deployZKSyncRegistry(AutoBase.PayoutMode.OFF_CHAIN); + + vm.expectRevert(abi.encodeWithSelector(Registry.InvalidToken.selector)); + registry.setConfigTypeSafe( + SIGNERS, + TRANSMITTERS, + F, + cfg, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes, + billingTokens, + billingConfigs + ); + } + + function testSetConfigRevertDueToInvalidDecimals() public { + address[] memory billingTokens = new address[](1); + billingTokens[0] = address(linkToken); + + AutomationRegistryBase2_3.BillingConfig[] memory billingConfigs = new AutomationRegistryBase2_3.BillingConfig[](1); + billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_000, + flatFeeMilliCents: 20_000, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 2_000_000_000, // $20 + minSpend: 100_000, + decimals: 6 // link token should have 18 decimals + }); + + vm.expectRevert(abi.encodeWithSelector(Registry.InvalidToken.selector)); + registry.setConfigTypeSafe( + SIGNERS, + TRANSMITTERS, + F, + cfg, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes, + billingTokens, + billingConfigs + ); + } + + function testSetConfigOnTransmittersAndPayees() public { + registry.setPayees(PAYEES); + AutomationRegistryBase2_3.TransmitterPayeeInfo[] memory transmitterPayeeInfos = registry + .getTransmittersWithPayees(); + assertEq(transmitterPayeeInfos.length, TRANSMITTERS.length); + + for (uint256 i = 0; i < transmitterPayeeInfos.length; i++) { + address transmitterAddress = transmitterPayeeInfos[i].transmitterAddress; + address payeeAddress = transmitterPayeeInfos[i].payeeAddress; + + address expectedTransmitter = TRANSMITTERS[i]; + address expectedPayee = PAYEES[i]; + + assertEq(transmitterAddress, expectedTransmitter); + assertEq(payeeAddress, expectedPayee); + } + } + + function testSetConfigWithNewTransmittersSuccess() public { + registry = deployZKSyncRegistry(AutoBase.PayoutMode.OFF_CHAIN); + + (uint32 configCount, uint32 blockNumber, ) = registry.latestConfigDetails(); + assertEq(configCount, 0); + + address billingTokenAddress = address(usdToken18); + address[] memory billingTokens = new address[](1); + billingTokens[0] = billingTokenAddress; + + AutomationRegistryBase2_3.BillingConfig[] memory billingConfigs = new AutomationRegistryBase2_3.BillingConfig[](1); + billingConfigs[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 5_000, + flatFeeMilliCents: 20_000, + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 2_000_000_000, // $20 + minSpend: 100_000, + decimals: 18 + }); + + bytes memory onchainConfigBytes = abi.encode(cfg); + + bytes32 configDigest = _configDigestFromConfigData( + block.chainid, + address(registry), + ++configCount, + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytes, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + vm.expectEmit(); + emit ConfigSet( + blockNumber, + configDigest, + configCount, + SIGNERS, + TRANSMITTERS, + F, + onchainConfigBytes, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + registry.setConfigTypeSafe( + SIGNERS, + TRANSMITTERS, + F, + cfg, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes, + billingTokens, + billingConfigs + ); + + (, , address[] memory signers, address[] memory transmitters, ) = registry.getState(); + assertEq(signers, SIGNERS); + assertEq(transmitters, TRANSMITTERS); + + (configCount, blockNumber, ) = registry.latestConfigDetails(); + configDigest = _configDigestFromConfigData( + block.chainid, + address(registry), + ++configCount, + SIGNERS, + NEW_TRANSMITTERS, + F, + onchainConfigBytes, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + vm.expectEmit(); + emit ConfigSet( + blockNumber, + configDigest, + configCount, + SIGNERS, + NEW_TRANSMITTERS, + F, + onchainConfigBytes, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes + ); + + registry.setConfigTypeSafe( + SIGNERS, + NEW_TRANSMITTERS, + F, + cfg, + OFFCHAIN_CONFIG_VERSION, + offchainConfigBytes, + billingTokens, + billingConfigs + ); + + (, , signers, transmitters, ) = registry.getState(); + assertEq(signers, SIGNERS); + assertEq(transmitters, NEW_TRANSMITTERS); + } + + function _getRegistrars() private pure returns (address[] memory) { + address[] memory registrars = new address[](2); + registrars[0] = address(uint160(uint256(keccak256("registrar1")))); + registrars[1] = address(uint160(uint256(keccak256("registrar2")))); + return registrars; + } + + function _configDigestFromConfigData( + uint256 chainId, + address contractAddress, + uint64 configCount, + address[] memory signers, + address[] memory transmitters, + uint8 f, + bytes memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) internal pure returns (bytes32) { + uint256 h = uint256( + keccak256( + abi.encode( + chainId, + contractAddress, + configCount, + signers, + transmitters, + f, + onchainConfig, + offchainConfigVersion, + offchainConfig + ) + ) + ); + uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00 + uint256 prefix = 0x0001 << (256 - 16); // 0x000100..00 + return bytes32((prefix & prefixMask) | (h & ~prefixMask)); + } +} + +contract NOPsSettlement is SetUp { + event NOPsSettledOffchain(address[] payees, uint256[] payments); + event FundsWithdrawn(uint256 indexed id, uint256 amount, address to); + event PaymentWithdrawn(address indexed transmitter, uint256 indexed amount, address indexed to, address payee); + + function testSettleNOPsOffchainRevertDueToUnauthorizedCaller() public { + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN); + + vm.expectRevert(abi.encodeWithSelector(Registry.OnlyFinanceAdmin.selector)); + registry.settleNOPsOffchain(); + } + + function testSettleNOPsOffchainRevertDueToOffchainSettlementDisabled() public { + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + + vm.prank(registry.owner()); + registry.disableOffchainPayments(); + + vm.prank(FINANCE_ADMIN); + vm.expectRevert(abi.encodeWithSelector(Registry.MustSettleOnchain.selector)); + registry.settleNOPsOffchain(); + } + + function testSettleNOPsOffchainSuccess() public { + // deploy and configure a registry with OFF_CHAIN payout + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + registry.setPayees(PAYEES); + + uint256[] memory payments = new uint256[](TRANSMITTERS.length); + for (uint256 i = 0; i < TRANSMITTERS.length; i++) { + payments[i] = 0; + } + + vm.startPrank(FINANCE_ADMIN); + vm.expectEmit(); + emit NOPsSettledOffchain(PAYEES, payments); + registry.settleNOPsOffchain(); + } + + // 1. transmitter balance zeroed after settlement, 2. admin can withdraw ERC20, 3. switch to onchain mode, 4. link amount owed to NOPs stays the same + function testSettleNOPsOffchainSuccessWithERC20MultiSteps() public { + // deploy and configure a registry with OFF_CHAIN payout + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + registry.setPayees(PAYEES); + + // register an upkeep and add funds + uint256 id = registry.registerUpkeep(address(TARGET1), 1000000, UPKEEP_ADMIN, 0, address(usdToken18), "", "", ""); + _mintERC20_18Decimals(UPKEEP_ADMIN, 1e20); + vm.startPrank(UPKEEP_ADMIN); + usdToken18.approve(address(registry), 1e20); + registry.addFunds(id, 1e20); + + // manually create a transmit so transmitters earn some rewards + _transmit(id, registry, bytes4(0)); + + // verify transmitters have positive balances + uint256[] memory payments = new uint256[](TRANSMITTERS.length); + for (uint256 i = 0; i < TRANSMITTERS.length; i++) { + (bool active, uint8 index, uint96 balance, uint96 lastCollected, ) = registry.getTransmitterInfo(TRANSMITTERS[i]); + assertTrue(active); + assertEq(i, index); + assertTrue(balance > 0); + assertEq(0, lastCollected); + + payments[i] = balance; + } + + // verify offchain settlement will emit NOPs' balances + vm.startPrank(FINANCE_ADMIN); + vm.expectEmit(); + emit NOPsSettledOffchain(PAYEES, payments); + registry.settleNOPsOffchain(); + + // verify that transmitters balance has been zeroed out + for (uint256 i = 0; i < TRANSMITTERS.length; i++) { + (bool active, uint8 index, uint96 balance, , ) = registry.getTransmitterInfo(TRANSMITTERS[i]); + assertTrue(active); + assertEq(i, index); + assertEq(0, balance); + } + + // after the offchain settlement, the total reserve amount of LINK should be 0 + assertEq(registry.getReserveAmount(address(linkToken)), 0); + // should have some ERC20s in registry after transmit + uint256 erc20ForPayment1 = registry.getAvailableERC20ForPayment(address(usdToken18)); + require(erc20ForPayment1 > 0, "ERC20AvailableForPayment should be positive"); + + vm.startPrank(UPKEEP_ADMIN); + vm.roll(100 + block.number); + // manually create a transmit so transmitters earn some rewards + _transmit(id, registry, bytes4(0)); + + uint256 erc20ForPayment2 = registry.getAvailableERC20ForPayment(address(usdToken18)); + require(erc20ForPayment2 > erc20ForPayment1, "ERC20AvailableForPayment should be greater after another transmit"); + + // finance admin comes to withdraw all available ERC20s + vm.startPrank(FINANCE_ADMIN); + registry.withdrawERC20Fees(address(usdToken18), FINANCE_ADMIN, erc20ForPayment2); + + uint256 erc20ForPayment3 = registry.getAvailableERC20ForPayment(address(usdToken18)); + require(erc20ForPayment3 == 0, "ERC20AvailableForPayment should be 0 now after withdrawal"); + + uint256 reservedLink = registry.getReserveAmount(address(linkToken)); + require(reservedLink > 0, "Reserve amount of LINK should be positive since there was another transmit"); + + // owner comes to disable offchain mode + vm.startPrank(registry.owner()); + registry.disableOffchainPayments(); + + // finance admin comes to withdraw all available ERC20s, should revert bc of insufficient link liquidity + vm.startPrank(FINANCE_ADMIN); + uint256 erc20ForPayment4 = registry.getAvailableERC20ForPayment(address(usdToken18)); + vm.expectRevert(abi.encodeWithSelector(Registry.InsufficientLinkLiquidity.selector)); + registry.withdrawERC20Fees(address(usdToken18), FINANCE_ADMIN, erc20ForPayment4); + + // reserved link amount to NOPs should stay the same after switching to onchain mode + assertEq(registry.getReserveAmount(address(linkToken)), reservedLink); + // available ERC20 for payment should be 0 since finance admin withdrew all already + assertEq(erc20ForPayment4, 0); + } + + function testSettleNOPsOffchainForDeactivatedTransmittersSuccess() public { + // deploy and configure a registry with OFF_CHAIN payout + (Registry registry, Registrar registrar) = deployAndConfigureZKSyncRegistryAndRegistrar( + AutoBase.PayoutMode.OFF_CHAIN + ); + + // register an upkeep and add funds + uint256 id = registry.registerUpkeep(address(TARGET1), 1000000, UPKEEP_ADMIN, 0, address(usdToken18), "", "", ""); + _mintERC20_18Decimals(UPKEEP_ADMIN, 1e20); + vm.startPrank(UPKEEP_ADMIN); + usdToken18.approve(address(registry), 1e20); + registry.addFunds(id, 1e20); + + // manually create a transmit so TRANSMITTERS earn some rewards + _transmit(id, registry, bytes4(0)); + + // TRANSMITTERS have positive balance now + // configure the registry to use NEW_TRANSMITTERS + _configureWithNewTransmitters(registry, registrar); + + _transmit(id, registry, bytes4(0)); + + // verify all transmitters have positive balances + address[] memory expectedPayees = new address[](6); + uint256[] memory expectedPayments = new uint256[](6); + for (uint256 i = 0; i < NEW_TRANSMITTERS.length; i++) { + (bool active, uint8 index, uint96 balance, uint96 lastCollected, address payee) = registry.getTransmitterInfo( + NEW_TRANSMITTERS[i] + ); + assertTrue(active); + assertEq(i, index); + assertTrue(lastCollected > 0); + expectedPayments[i] = balance; + expectedPayees[i] = payee; + } + for (uint256 i = 2; i < TRANSMITTERS.length; i++) { + (bool active, uint8 index, uint96 balance, uint96 lastCollected, address payee) = registry.getTransmitterInfo( + TRANSMITTERS[i] + ); + assertFalse(active); + assertEq(i, index); + assertTrue(balance > 0); + assertTrue(lastCollected > 0); + expectedPayments[2 + i] = balance; + expectedPayees[2 + i] = payee; + } + + // verify offchain settlement will emit NOPs' balances + vm.startPrank(FINANCE_ADMIN); + + // simply expectEmit won't work here because s_deactivatedTransmitters is an enumerable set so the order of these + // deactivated transmitters is not guaranteed. To handle this, we record logs and decode data field manually. + vm.recordLogs(); + registry.settleNOPsOffchain(); + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries.length, 1); + Vm.Log memory l = entries[0]; + assertEq(l.topics[0], keccak256("NOPsSettledOffchain(address[],uint256[])")); + (address[] memory actualPayees, uint256[] memory actualPayments) = abi.decode(l.data, (address[], uint256[])); + assertEq(actualPayees.length, 6); + assertEq(actualPayments.length, 6); + + // first 4 payees and payments are for NEW_TRANSMITTERS and they are ordered. + for (uint256 i = 0; i < NEW_TRANSMITTERS.length; i++) { + assertEq(actualPayees[i], expectedPayees[i]); + assertEq(actualPayments[i], expectedPayments[i]); + } + + // the last 2 payees and payments for TRANSMITTERS[2] and TRANSMITTERS[3] and they are not ordered + assertTrue( + (actualPayments[5] == expectedPayments[5] && + actualPayees[5] == expectedPayees[5] && + actualPayments[4] == expectedPayments[4] && + actualPayees[4] == expectedPayees[4]) || + (actualPayments[5] == expectedPayments[4] && + actualPayees[5] == expectedPayees[4] && + actualPayments[4] == expectedPayments[5] && + actualPayees[4] == expectedPayees[5]) + ); + + // verify that new transmitters balance has been zeroed out + for (uint256 i = 0; i < NEW_TRANSMITTERS.length; i++) { + (bool active, uint8 index, uint96 balance, , ) = registry.getTransmitterInfo(NEW_TRANSMITTERS[i]); + assertTrue(active); + assertEq(i, index); + assertEq(0, balance); + } + // verify that deactivated transmitters (TRANSMITTERS[2] and TRANSMITTERS[3]) balance has been zeroed out + for (uint256 i = 2; i < TRANSMITTERS.length; i++) { + (bool active, uint8 index, uint96 balance, , ) = registry.getTransmitterInfo(TRANSMITTERS[i]); + assertFalse(active); + assertEq(i, index); + assertEq(0, balance); + } + + // after the offchain settlement, the total reserve amount of LINK should be 0 + assertEq(registry.getReserveAmount(address(linkToken)), 0); + } + + function testDisableOffchainPaymentsRevertDueToUnauthorizedCaller() public { + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + + vm.startPrank(FINANCE_ADMIN); + vm.expectRevert(bytes("Only callable by owner")); + registry.disableOffchainPayments(); + } + + function testDisableOffchainPaymentsSuccess() public { + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + + vm.startPrank(registry.owner()); + registry.disableOffchainPayments(); + + assertEq(uint8(AutoBase.PayoutMode.ON_CHAIN), registry.getPayoutMode()); + } + + function testSinglePerformAndNodesCanWithdrawOnchain() public { + // deploy and configure a registry with OFF_CHAIN payout + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + registry.setPayees(PAYEES); + + // register an upkeep and add funds + uint256 id = registry.registerUpkeep(address(TARGET1), 1000000, UPKEEP_ADMIN, 0, address(usdToken18), "", "", ""); + _mintERC20_18Decimals(UPKEEP_ADMIN, 1e20); + vm.startPrank(UPKEEP_ADMIN); + usdToken18.approve(address(registry), 1e20); + registry.addFunds(id, 1e20); + + // manually create a transmit so transmitters earn some rewards + _transmit(id, registry, bytes4(0)); + + // disable offchain payments + _mintLink(address(registry), 1e19); + vm.prank(registry.owner()); + registry.disableOffchainPayments(); + + // payees should be able to withdraw onchain + for (uint256 i = 0; i < TRANSMITTERS.length; i++) { + (, , uint96 balance, , address payee) = registry.getTransmitterInfo(TRANSMITTERS[i]); + vm.prank(payee); + vm.expectEmit(); + emit PaymentWithdrawn(TRANSMITTERS[i], balance, payee, payee); + registry.withdrawPayment(TRANSMITTERS[i], payee); + } + + // allow upkeep admin to withdraw funds + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(id); + vm.roll(100 + block.number); + vm.expectEmit(); + // the upkeep spent less than minimum spending limit so upkeep admin can only withdraw upkeep balance - min spend value + emit FundsWithdrawn(id, 9.9e19, UPKEEP_ADMIN); + registry.withdrawFunds(id, UPKEEP_ADMIN); + } + + function testMultiplePerformsAndNodesCanWithdrawOnchain() public { + // deploy and configure a registry with OFF_CHAIN payout + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.OFF_CHAIN); + registry.setPayees(PAYEES); + + // register an upkeep and add funds + uint256 id = registry.registerUpkeep(address(TARGET1), 1000000, UPKEEP_ADMIN, 0, address(usdToken18), "", "", ""); + _mintERC20_18Decimals(UPKEEP_ADMIN, 1e20); + vm.startPrank(UPKEEP_ADMIN); + usdToken18.approve(address(registry), 1e20); + registry.addFunds(id, 1e20); + + // manually call transmit so transmitters earn some rewards + for (uint256 i = 0; i < 50; i++) { + vm.roll(100 + block.number); + _transmit(id, registry, bytes4(0)); + } + + // disable offchain payments + _mintLink(address(registry), 1e19); + vm.prank(registry.owner()); + registry.disableOffchainPayments(); + + // manually call transmit after offchain payment is disabled + for (uint256 i = 0; i < 50; i++) { + vm.roll(100 + block.number); + _transmit(id, registry, bytes4(0)); + } + + // payees should be able to withdraw onchain + for (uint256 i = 0; i < TRANSMITTERS.length; i++) { + (, , uint96 balance, , address payee) = registry.getTransmitterInfo(TRANSMITTERS[i]); + vm.prank(payee); + vm.expectEmit(); + emit PaymentWithdrawn(TRANSMITTERS[i], balance, payee, payee); + registry.withdrawPayment(TRANSMITTERS[i], payee); + } + + // allow upkeep admin to withdraw funds + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(id); + vm.roll(100 + block.number); + uint256 balance = registry.getBalance(id); + vm.expectEmit(); + emit FundsWithdrawn(id, balance, UPKEEP_ADMIN); + registry.withdrawFunds(id, UPKEEP_ADMIN); + } + + function _configureWithNewTransmitters(Registry registry, Registrar registrar) internal { + IERC20[] memory billingTokens = new IERC20[](1); + billingTokens[0] = IERC20(address(usdToken18)); + + uint256[] memory minRegistrationFees = new uint256[](billingTokens.length); + minRegistrationFees[0] = 100e18; // 100 USD + + address[] memory billingTokenAddresses = new address[](billingTokens.length); + for (uint256 i = 0; i < billingTokens.length; i++) { + billingTokenAddresses[i] = address(billingTokens[i]); + } + + AutomationRegistryBase2_3.BillingConfig[] + memory billingTokenConfigs = new AutomationRegistryBase2_3.BillingConfig[](billingTokens.length); + billingTokenConfigs[0] = AutomationRegistryBase2_3.BillingConfig({ + gasFeePPB: 10_000_000, // 15% + flatFeeMilliCents: 2_000, // 2 cents + priceFeed: address(USDTOKEN_USD_FEED), + fallbackPrice: 1e8, // $1 + minSpend: 1e18, // 1 USD + decimals: 18 + }); + + address[] memory registrars = new address[](1); + registrars[0] = address(registrar); + + AutomationRegistryBase2_3.OnchainConfig memory cfg = AutomationRegistryBase2_3.OnchainConfig({ + checkGasLimit: 5_000_000, + stalenessSeconds: 90_000, + gasCeilingMultiplier: 2, + maxPerformGas: 10_000_000, + maxCheckDataSize: 5_000, + maxPerformDataSize: 5_000, + maxRevertDataSize: 5_000, + fallbackGasPrice: 20_000_000_000, + fallbackLinkPrice: 2_000_000_000, // $20 + fallbackNativePrice: 400_000_000_000, // $4,000 + transcoder: 0xB1e66855FD67f6e85F0f0fA38cd6fBABdf00923c, + registrars: registrars, + upkeepPrivilegeManager: PRIVILEGE_MANAGER, + chainModule: address(new ChainModuleBase()), + reorgProtectionEnabled: true, + financeAdmin: FINANCE_ADMIN + }); + + registry.setConfigTypeSafe( + SIGNERS, + NEW_TRANSMITTERS, + F, + cfg, + OFFCHAIN_CONFIG_VERSION, + "", + billingTokenAddresses, + billingTokenConfigs + ); + + registry.setPayees(NEW_PAYEES); + } +} + +contract WithdrawPayment is SetUp { + function testWithdrawPaymentRevertDueToOffchainPayoutMode() public { + registry = deployZKSyncRegistry(AutoBase.PayoutMode.OFF_CHAIN); + vm.expectRevert(abi.encodeWithSelector(Registry.MustSettleOffchain.selector)); + vm.prank(TRANSMITTERS[0]); + registry.withdrawPayment(TRANSMITTERS[0], TRANSMITTERS[0]); + } +} + +contract RegisterUpkeep is SetUp { + function test_RevertsWhen_Paused() public { + registry.pause(); + vm.expectRevert(Registry.RegistryPaused.selector); + registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + "", + "", + "" + ); + } + + function test_RevertsWhen_TargetIsNotAContract() public { + vm.expectRevert(Registry.NotAContract.selector); + registry.registerUpkeep( + randomAddress(), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + "", + "", + "" + ); + } + + function test_RevertsWhen_CalledByNonOwner() public { + vm.prank(STRANGER); + vm.expectRevert(Registry.OnlyCallableByOwnerOrRegistrar.selector); + registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + "", + "", + "" + ); + } + + function test_RevertsWhen_ExecuteGasIsTooLow() public { + vm.expectRevert(Registry.GasLimitOutsideRange.selector); + registry.registerUpkeep( + address(TARGET1), + 2299, // 1 less than min + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + "", + "", + "" + ); + } + + function test_RevertsWhen_ExecuteGasIsTooHigh() public { + vm.expectRevert(Registry.GasLimitOutsideRange.selector); + registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas + 1, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + "", + "", + "" + ); + } + + function test_RevertsWhen_TheBillingTokenIsNotConfigured() public { + vm.expectRevert(Registry.InvalidToken.selector); + registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + randomAddress(), + "", + "", + "" + ); + } + + function test_RevertsWhen_CheckDataIsTooLarge() public { + vm.expectRevert(Registry.CheckDataExceedsLimit.selector); + registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + randomBytes(config.maxCheckDataSize + 1), + "", + "" + ); + } + + function test_Happy() public { + bytes memory checkData = randomBytes(config.maxCheckDataSize); + bytes memory trigggerConfig = randomBytes(100); + bytes memory offchainConfig = randomBytes(100); + + uint256 upkeepCount = registry.getNumUpkeeps(); + + uint256 upkeepID = registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.LOG), + address(linkToken), + checkData, + trigggerConfig, + offchainConfig + ); + + assertEq(registry.getNumUpkeeps(), upkeepCount + 1); + assertEq(registry.getUpkeep(upkeepID).target, address(TARGET1)); + assertEq(registry.getUpkeep(upkeepID).performGas, config.maxPerformGas); + assertEq(registry.getUpkeep(upkeepID).checkData, checkData); + assertEq(registry.getUpkeep(upkeepID).balance, 0); + assertEq(registry.getUpkeep(upkeepID).admin, UPKEEP_ADMIN); + assertEq(registry.getUpkeep(upkeepID).offchainConfig, offchainConfig); + assertEq(registry.getUpkeepTriggerConfig(upkeepID), trigggerConfig); + assertEq(uint8(registry.getTriggerType(upkeepID)), uint8(Trigger.LOG)); + } +} + +contract OnTokenTransfer is SetUp { + function test_RevertsWhen_NotCalledByTheLinkToken() public { + vm.expectRevert(Registry.OnlyCallableByLINKToken.selector); + registry.onTokenTransfer(UPKEEP_ADMIN, 100, abi.encode(linkUpkeepID)); + } + + function test_RevertsWhen_NotCalledWithExactly32Bytes() public { + vm.startPrank(address(linkToken)); + vm.expectRevert(Registry.InvalidDataLength.selector); + registry.onTokenTransfer(UPKEEP_ADMIN, 100, randomBytes(31)); + vm.expectRevert(Registry.InvalidDataLength.selector); + registry.onTokenTransfer(UPKEEP_ADMIN, 100, randomBytes(33)); + } + + function test_RevertsWhen_TheUpkeepIsCancelledOrDNE() public { + vm.startPrank(address(linkToken)); + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.onTokenTransfer(UPKEEP_ADMIN, 100, abi.encode(randomNumber())); + } + + function test_RevertsWhen_TheUpkeepDoesNotUseLINKAsItsBillingToken() public { + vm.startPrank(address(linkToken)); + vm.expectRevert(Registry.InvalidToken.selector); + registry.onTokenTransfer(UPKEEP_ADMIN, 100, abi.encode(usdUpkeepID18)); + } + + function test_Happy() public { + vm.startPrank(address(linkToken)); + uint256 beforeBalance = registry.getBalance(linkUpkeepID); + registry.onTokenTransfer(UPKEEP_ADMIN, 100, abi.encode(linkUpkeepID)); + assertEq(registry.getBalance(linkUpkeepID), beforeBalance + 100); + } +} + +contract GetMinBalanceForUpkeep is SetUp { + function test_accountsForFlatFee_with18Decimals() public { + // set fee to 0 + AutomationRegistryBase2_3.BillingConfig memory usdTokenConfig = registry.getBillingTokenConfig(address(usdToken18)); + usdTokenConfig.flatFeeMilliCents = 0; + _updateBillingTokenConfig(registry, address(usdToken18), usdTokenConfig); + + uint256 minBalanceBefore = registry.getMinBalanceForUpkeep(usdUpkeepID18); + + // set fee to non-zero + usdTokenConfig.flatFeeMilliCents = 100; + _updateBillingTokenConfig(registry, address(usdToken18), usdTokenConfig); + + uint256 minBalanceAfter = registry.getMinBalanceForUpkeep(usdUpkeepID18); + assertEq( + minBalanceAfter, + minBalanceBefore + ((uint256(usdTokenConfig.flatFeeMilliCents) * 1e13) / 10 ** (18 - usdTokenConfig.decimals)) + ); + } + + function test_accountsForFlatFee_with6Decimals() public { + // set fee to 0 + AutomationRegistryBase2_3.BillingConfig memory usdTokenConfig = registry.getBillingTokenConfig(address(usdToken6)); + usdTokenConfig.flatFeeMilliCents = 0; + _updateBillingTokenConfig(registry, address(usdToken6), usdTokenConfig); + + uint256 minBalanceBefore = registry.getMinBalanceForUpkeep(usdUpkeepID6); + + // set fee to non-zero + usdTokenConfig.flatFeeMilliCents = 100; + _updateBillingTokenConfig(registry, address(usdToken6), usdTokenConfig); + + uint256 minBalanceAfter = registry.getMinBalanceForUpkeep(usdUpkeepID6); + assertEq( + minBalanceAfter, + minBalanceBefore + ((uint256(usdTokenConfig.flatFeeMilliCents) * 1e13) / 10 ** (18 - usdTokenConfig.decimals)) + ); + } +} + +contract BillingOverrides is SetUp { + event BillingConfigOverridden(uint256 indexed id, AutomationRegistryBase2_3.BillingOverrides overrides); + event BillingConfigOverrideRemoved(uint256 indexed id); + + function test_RevertsWhen_NotPrivilegeManager() public { + AutomationRegistryBase2_3.BillingOverrides memory billingOverrides = AutomationRegistryBase2_3.BillingOverrides({ + gasFeePPB: 5_000, + flatFeeMilliCents: 20_000 + }); + + vm.expectRevert(Registry.OnlyCallableByUpkeepPrivilegeManager.selector); + registry.setBillingOverrides(linkUpkeepID, billingOverrides); + } + + function test_RevertsWhen_UpkeepCancelled() public { + AutomationRegistryBase2_3.BillingOverrides memory billingOverrides = AutomationRegistryBase2_3.BillingOverrides({ + gasFeePPB: 5_000, + flatFeeMilliCents: 20_000 + }); + + registry.cancelUpkeep(linkUpkeepID); + + vm.startPrank(PRIVILEGE_MANAGER); + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.setBillingOverrides(linkUpkeepID, billingOverrides); + } + + function test_Happy_SetBillingOverrides() public { + AutomationRegistryBase2_3.BillingOverrides memory billingOverrides = AutomationRegistryBase2_3.BillingOverrides({ + gasFeePPB: 5_000, + flatFeeMilliCents: 20_000 + }); + + vm.startPrank(PRIVILEGE_MANAGER); + + vm.expectEmit(); + emit BillingConfigOverridden(linkUpkeepID, billingOverrides); + registry.setBillingOverrides(linkUpkeepID, billingOverrides); + } + + function test_Happy_RemoveBillingOverrides() public { + vm.startPrank(PRIVILEGE_MANAGER); + + vm.expectEmit(); + emit BillingConfigOverrideRemoved(linkUpkeepID); + registry.removeBillingOverrides(linkUpkeepID); + } + + function test_Happy_MaxGasPayment_WithBillingOverrides() public { + uint96 maxPayment1 = registry.getMaxPaymentForGas(linkUpkeepID, 0, 5_000_000, address(linkToken)); + + // Double the two billing values + AutomationRegistryBase2_3.BillingOverrides memory billingOverrides = AutomationRegistryBase2_3.BillingOverrides({ + gasFeePPB: DEFAULT_GAS_FEE_PPB * 2, + flatFeeMilliCents: DEFAULT_FLAT_FEE_MILLI_CENTS * 2 + }); + + vm.startPrank(PRIVILEGE_MANAGER); + registry.setBillingOverrides(linkUpkeepID, billingOverrides); + + // maxPayment2 should be greater than maxPayment1 after the overrides + // The 2 numbers should follow this: maxPayment2 - maxPayment1 == 2 * recepit.premium + // We do not apply the exact equation since we couldn't get the receipt.premium value + uint96 maxPayment2 = registry.getMaxPaymentForGas(linkUpkeepID, 0, 5_000_000, address(linkToken)); + assertGt(maxPayment2, maxPayment1); + } +} + +contract Transmit is SetUp { + function test_transmitRevertWithExtraBytes() external { + bytes32[3] memory exampleReportContext = [ + bytes32(0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef), + bytes32(0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890), + bytes32(0x7890abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456) + ]; + + bytes memory exampleRawReport = hex"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + + bytes32[] memory exampleRs = new bytes32[](3); + exampleRs[0] = bytes32(0x1234561234561234561234561234561234561234561234561234561234561234); + exampleRs[1] = bytes32(0x1234561234561234561234561234561234561234561234561234561234561234); + exampleRs[2] = bytes32(0x7890789078907890789078907890789078907890789078907890789078907890); + + bytes32[] memory exampleSs = new bytes32[](3); + exampleSs[0] = bytes32(0x1234561234561234561234561234561234561234561234561234561234561234); + exampleSs[1] = bytes32(0x1234561234561234561234561234561234561234561234561234561234561234); + exampleSs[2] = bytes32(0x1234561234561234561234561234561234561234561234561234561234561234); + + bytes32 exampleRawVs = bytes32(0x1234561234561234561234561234561234561234561234561234561234561234); + + bytes memory transmitData = abi.encodeWithSelector( + registry.transmit.selector, + exampleReportContext, + exampleRawReport, + exampleRs, + exampleSs, + exampleRawVs + ); + bytes memory badTransmitData = bytes.concat(transmitData, bytes1(0x00)); // add extra data + vm.startPrank(TRANSMITTERS[0]); + (bool success, bytes memory returnData) = address(registry).call(badTransmitData); // send the bogus transmit + assertFalse(success, "Call did not revert as expected"); + assertEq(returnData, abi.encodePacked(Registry.InvalidDataLength.selector)); + vm.stopPrank(); + } + + function test_handlesMixedBatchOfBillingTokens() external { + uint256[] memory prevUpkeepBalances = new uint256[](3); + prevUpkeepBalances[0] = registry.getBalance(linkUpkeepID); + prevUpkeepBalances[1] = registry.getBalance(usdUpkeepID18); + prevUpkeepBalances[2] = registry.getBalance(nativeUpkeepID); + uint256[] memory prevTokenBalances = new uint256[](3); + prevTokenBalances[0] = linkToken.balanceOf(address(registry)); + prevTokenBalances[1] = usdToken18.balanceOf(address(registry)); + prevTokenBalances[2] = weth.balanceOf(address(registry)); + uint256[] memory prevReserveBalances = new uint256[](3); + prevReserveBalances[0] = registry.getReserveAmount(address(linkToken)); + prevReserveBalances[1] = registry.getReserveAmount(address(usdToken18)); + prevReserveBalances[2] = registry.getReserveAmount(address(weth)); + uint256[] memory upkeepIDs = new uint256[](3); + upkeepIDs[0] = linkUpkeepID; + upkeepIDs[1] = usdUpkeepID18; + upkeepIDs[2] = nativeUpkeepID; + + // withdraw-able by finance team should be 0 + require(registry.getAvailableERC20ForPayment(address(usdToken18)) == 0, "ERC20AvailableForPayment should be 0"); + require(registry.getAvailableERC20ForPayment(address(weth)) == 0, "ERC20AvailableForPayment should be 0"); + + // do the thing + _transmit(upkeepIDs, registry, bytes4(0)); + + // withdraw-able by the finance team should be positive + require( + registry.getAvailableERC20ForPayment(address(usdToken18)) > 0, + "ERC20AvailableForPayment should be positive" + ); + require(registry.getAvailableERC20ForPayment(address(weth)) > 0, "ERC20AvailableForPayment should be positive"); + + // assert upkeep balances have decreased + require(prevUpkeepBalances[0] > registry.getBalance(linkUpkeepID), "link upkeep balance should have decreased"); + require(prevUpkeepBalances[1] > registry.getBalance(usdUpkeepID18), "usd upkeep balance should have decreased"); + require(prevUpkeepBalances[2] > registry.getBalance(nativeUpkeepID), "native upkeep balance should have decreased"); + // assert token balances have not changed + assertEq(prevTokenBalances[0], linkToken.balanceOf(address(registry))); + assertEq(prevTokenBalances[1], usdToken18.balanceOf(address(registry))); + assertEq(prevTokenBalances[2], weth.balanceOf(address(registry))); + // assert reserve amounts have adjusted accordingly + require( + prevReserveBalances[0] < registry.getReserveAmount(address(linkToken)), + "usd reserve amount should have increased" + ); // link reserve amount increases in value equal to the decrease of the other reserve amounts + require( + prevReserveBalances[1] > registry.getReserveAmount(address(usdToken18)), + "usd reserve amount should have decreased" + ); + require( + prevReserveBalances[2] > registry.getReserveAmount(address(weth)), + "native reserve amount should have decreased" + ); + } + + function test_handlesInsufficientBalanceWithUSDToken18() external { + // deploy and configure a registry with ON_CHAIN payout + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN); + + // register an upkeep and add funds + uint256 upkeepID = registry.registerUpkeep( + address(TARGET1), + 1000000, + UPKEEP_ADMIN, + 0, + address(usdToken18), + "", + "", + "" + ); + _mintERC20_18Decimals(UPKEEP_ADMIN, 1e20); + vm.startPrank(UPKEEP_ADMIN); + usdToken18.approve(address(registry), 1e20); + registry.addFunds(upkeepID, 1); // smaller than gasCharge + uint256 balance = registry.getBalance(upkeepID); + + // manually create a transmit + vm.recordLogs(); + _transmit(upkeepID, registry, bytes4(0)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries.length, 3); + Vm.Log memory l1 = entries[1]; + assertEq( + l1.topics[0], + keccak256("UpkeepCharged(uint256,(uint96,uint96,uint96,uint96,address,uint96,uint96,uint96))") + ); + ( + uint96 gasChargeInBillingToken, + uint96 premiumInBillingToken, + uint96 gasReimbursementInJuels, + uint96 premiumInJuels, + address billingToken, + uint96 linkUSD, + uint96 nativeUSD, + uint96 billingUSD + ) = abi.decode(l1.data, (uint96, uint96, uint96, uint96, address, uint96, uint96, uint96)); + + assertEq(gasChargeInBillingToken, balance); + assertEq(gasReimbursementInJuels, (balance * billingUSD) / linkUSD); + assertEq(premiumInJuels, 0); + assertEq(premiumInBillingToken, 0); + } + + function test_handlesInsufficientBalanceWithUSDToken6() external { + // deploy and configure a registry with ON_CHAIN payout + (Registry registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN); + + // register an upkeep and add funds + uint256 upkeepID = registry.registerUpkeep( + address(TARGET1), + 1000000, + UPKEEP_ADMIN, + 0, + address(usdToken6), + "", + "", + "" + ); + vm.prank(OWNER); + usdToken6.mint(UPKEEP_ADMIN, 1e20); + + vm.startPrank(UPKEEP_ADMIN); + usdToken6.approve(address(registry), 1e20); + registry.addFunds(upkeepID, 100); // this is greater than gasCharge but less than (gasCharge + premium) + uint256 balance = registry.getBalance(upkeepID); + + // manually create a transmit + vm.recordLogs(); + _transmit(upkeepID, registry, bytes4(0)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries.length, 3); + Vm.Log memory l1 = entries[1]; + assertEq( + l1.topics[0], + keccak256("UpkeepCharged(uint256,(uint96,uint96,uint96,uint96,address,uint96,uint96,uint96))") + ); + ( + uint96 gasChargeInBillingToken, + uint96 premiumInBillingToken, + uint96 gasReimbursementInJuels, + uint96 premiumInJuels, + address billingToken, + uint96 linkUSD, + uint96 nativeUSD, + uint96 billingUSD + ) = abi.decode(l1.data, (uint96, uint96, uint96, uint96, address, uint96, uint96, uint96)); + + assertEq(premiumInJuels, (balance * billingUSD * 1e12) / linkUSD - gasReimbursementInJuels); // scale to 18 decimals + assertEq(premiumInBillingToken, (premiumInJuels * linkUSD + (billingUSD * 1e12 - 1)) / (billingUSD * 1e12)); + } +} + +contract MigrateReceive is SetUp { + event UpkeepMigrated(uint256 indexed id, uint256 remainingBalance, address destination); + event UpkeepReceived(uint256 indexed id, uint256 startingBalance, address importedFrom); + + Registry newRegistry; + uint256[] idsToMigrate; + + function setUp() public override { + super.setUp(); + (newRegistry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN); + idsToMigrate.push(linkUpkeepID); + idsToMigrate.push(linkUpkeepID2); + idsToMigrate.push(usdUpkeepID18); + idsToMigrate.push(nativeUpkeepID); + registry.setPeerRegistryMigrationPermission(address(newRegistry), 1); + newRegistry.setPeerRegistryMigrationPermission(address(registry), 2); + } + + function test_RevertsWhen_PermissionsNotSet() external { + // no permissions + registry.setPeerRegistryMigrationPermission(address(newRegistry), 0); + newRegistry.setPeerRegistryMigrationPermission(address(registry), 0); + vm.expectRevert(Registry.MigrationNotPermitted.selector); + vm.prank(UPKEEP_ADMIN); + registry.migrateUpkeeps(idsToMigrate, address(newRegistry)); + + // only outgoing permissions + registry.setPeerRegistryMigrationPermission(address(newRegistry), 1); + newRegistry.setPeerRegistryMigrationPermission(address(registry), 0); + vm.expectRevert(Registry.MigrationNotPermitted.selector); + vm.prank(UPKEEP_ADMIN); + registry.migrateUpkeeps(idsToMigrate, address(newRegistry)); + + // only incoming permissions + registry.setPeerRegistryMigrationPermission(address(newRegistry), 0); + newRegistry.setPeerRegistryMigrationPermission(address(registry), 2); + vm.expectRevert(Registry.MigrationNotPermitted.selector); + vm.prank(UPKEEP_ADMIN); + registry.migrateUpkeeps(idsToMigrate, address(newRegistry)); + + // permissions opposite direction + registry.setPeerRegistryMigrationPermission(address(newRegistry), 2); + newRegistry.setPeerRegistryMigrationPermission(address(registry), 1); + vm.expectRevert(Registry.MigrationNotPermitted.selector); + vm.prank(UPKEEP_ADMIN); + registry.migrateUpkeeps(idsToMigrate, address(newRegistry)); + } + + function test_RevertsWhen_ReceivingRegistryDoesNotSupportToken() external { + _removeBillingTokenConfig(newRegistry, address(weth)); + vm.expectRevert(Registry.InvalidToken.selector); + vm.prank(UPKEEP_ADMIN); + registry.migrateUpkeeps(idsToMigrate, address(newRegistry)); + idsToMigrate.pop(); // remove native upkeep id + vm.prank(UPKEEP_ADMIN); + registry.migrateUpkeeps(idsToMigrate, address(newRegistry)); // should succeed now + } + + function test_RevertsWhen_CalledByNonAdmin() external { + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + vm.prank(STRANGER); + registry.migrateUpkeeps(idsToMigrate, address(newRegistry)); + } + + function test_Success() external { + vm.startPrank(UPKEEP_ADMIN); + + // add some changes in upkeep data to the mix + registry.pauseUpkeep(usdUpkeepID18); + registry.setUpkeepTriggerConfig(linkUpkeepID, randomBytes(100)); + registry.setUpkeepCheckData(nativeUpkeepID, randomBytes(25)); + + // record previous state + uint256[] memory prevUpkeepBalances = new uint256[](4); + prevUpkeepBalances[0] = registry.getBalance(linkUpkeepID); + prevUpkeepBalances[1] = registry.getBalance(linkUpkeepID2); + prevUpkeepBalances[2] = registry.getBalance(usdUpkeepID18); + prevUpkeepBalances[3] = registry.getBalance(nativeUpkeepID); + uint256[] memory prevReserveBalances = new uint256[](3); + prevReserveBalances[0] = registry.getReserveAmount(address(linkToken)); + prevReserveBalances[1] = registry.getReserveAmount(address(usdToken18)); + prevReserveBalances[2] = registry.getReserveAmount(address(weth)); + uint256[] memory prevTokenBalances = new uint256[](3); + prevTokenBalances[0] = linkToken.balanceOf(address(registry)); + prevTokenBalances[1] = usdToken18.balanceOf(address(registry)); + prevTokenBalances[2] = weth.balanceOf(address(registry)); + bytes[] memory prevUpkeepData = new bytes[](4); + prevUpkeepData[0] = abi.encode(registry.getUpkeep(linkUpkeepID)); + prevUpkeepData[1] = abi.encode(registry.getUpkeep(linkUpkeepID2)); + prevUpkeepData[2] = abi.encode(registry.getUpkeep(usdUpkeepID18)); + prevUpkeepData[3] = abi.encode(registry.getUpkeep(nativeUpkeepID)); + bytes[] memory prevUpkeepTriggerData = new bytes[](4); + prevUpkeepTriggerData[0] = registry.getUpkeepTriggerConfig(linkUpkeepID); + prevUpkeepTriggerData[1] = registry.getUpkeepTriggerConfig(linkUpkeepID2); + prevUpkeepTriggerData[2] = registry.getUpkeepTriggerConfig(usdUpkeepID18); + prevUpkeepTriggerData[3] = registry.getUpkeepTriggerConfig(nativeUpkeepID); + + // event expectations + vm.expectEmit(address(registry)); + emit UpkeepMigrated(linkUpkeepID, prevUpkeepBalances[0], address(newRegistry)); + vm.expectEmit(address(registry)); + emit UpkeepMigrated(linkUpkeepID2, prevUpkeepBalances[1], address(newRegistry)); + vm.expectEmit(address(registry)); + emit UpkeepMigrated(usdUpkeepID18, prevUpkeepBalances[2], address(newRegistry)); + vm.expectEmit(address(registry)); + emit UpkeepMigrated(nativeUpkeepID, prevUpkeepBalances[3], address(newRegistry)); + vm.expectEmit(address(newRegistry)); + emit UpkeepReceived(linkUpkeepID, prevUpkeepBalances[0], address(registry)); + vm.expectEmit(address(newRegistry)); + emit UpkeepReceived(linkUpkeepID2, prevUpkeepBalances[1], address(registry)); + vm.expectEmit(address(newRegistry)); + emit UpkeepReceived(usdUpkeepID18, prevUpkeepBalances[2], address(registry)); + vm.expectEmit(address(newRegistry)); + emit UpkeepReceived(nativeUpkeepID, prevUpkeepBalances[3], address(registry)); + + // do the thing + registry.migrateUpkeeps(idsToMigrate, address(newRegistry)); + + // assert upkeep balances have been migrated + assertEq(registry.getBalance(linkUpkeepID), 0); + assertEq(registry.getBalance(linkUpkeepID2), 0); + assertEq(registry.getBalance(usdUpkeepID18), 0); + assertEq(registry.getBalance(nativeUpkeepID), 0); + assertEq(newRegistry.getBalance(linkUpkeepID), prevUpkeepBalances[0]); + assertEq(newRegistry.getBalance(linkUpkeepID2), prevUpkeepBalances[1]); + assertEq(newRegistry.getBalance(usdUpkeepID18), prevUpkeepBalances[2]); + assertEq(newRegistry.getBalance(nativeUpkeepID), prevUpkeepBalances[3]); + + // assert reserve balances have been adjusted + assertEq( + newRegistry.getReserveAmount(address(linkToken)), + newRegistry.getBalance(linkUpkeepID) + newRegistry.getBalance(linkUpkeepID2) + ); + assertEq(newRegistry.getReserveAmount(address(usdToken18)), newRegistry.getBalance(usdUpkeepID18)); + assertEq(newRegistry.getReserveAmount(address(weth)), newRegistry.getBalance(nativeUpkeepID)); + assertEq( + newRegistry.getReserveAmount(address(linkToken)), + prevReserveBalances[0] - registry.getReserveAmount(address(linkToken)) + ); + assertEq( + newRegistry.getReserveAmount(address(usdToken18)), + prevReserveBalances[1] - registry.getReserveAmount(address(usdToken18)) + ); + assertEq( + newRegistry.getReserveAmount(address(weth)), + prevReserveBalances[2] - registry.getReserveAmount(address(weth)) + ); + + // assert token have been transferred + assertEq( + linkToken.balanceOf(address(newRegistry)), + newRegistry.getBalance(linkUpkeepID) + newRegistry.getBalance(linkUpkeepID2) + ); + assertEq(usdToken18.balanceOf(address(newRegistry)), newRegistry.getBalance(usdUpkeepID18)); + assertEq(weth.balanceOf(address(newRegistry)), newRegistry.getBalance(nativeUpkeepID)); + assertEq(linkToken.balanceOf(address(registry)), prevTokenBalances[0] - linkToken.balanceOf(address(newRegistry))); + assertEq( + usdToken18.balanceOf(address(registry)), + prevTokenBalances[1] - usdToken18.balanceOf(address(newRegistry)) + ); + assertEq(weth.balanceOf(address(registry)), prevTokenBalances[2] - weth.balanceOf(address(newRegistry))); + + // assert upkeep data matches + assertEq(prevUpkeepData[0], abi.encode(newRegistry.getUpkeep(linkUpkeepID))); + assertEq(prevUpkeepData[1], abi.encode(newRegistry.getUpkeep(linkUpkeepID2))); + assertEq(prevUpkeepData[2], abi.encode(newRegistry.getUpkeep(usdUpkeepID18))); + assertEq(prevUpkeepData[3], abi.encode(newRegistry.getUpkeep(nativeUpkeepID))); + assertEq(prevUpkeepTriggerData[0], newRegistry.getUpkeepTriggerConfig(linkUpkeepID)); + assertEq(prevUpkeepTriggerData[1], newRegistry.getUpkeepTriggerConfig(linkUpkeepID2)); + assertEq(prevUpkeepTriggerData[2], newRegistry.getUpkeepTriggerConfig(usdUpkeepID18)); + assertEq(prevUpkeepTriggerData[3], newRegistry.getUpkeepTriggerConfig(nativeUpkeepID)); + + vm.stopPrank(); + } +} + +contract Pause is SetUp { + function test_RevertsWhen_CalledByNonOwner() external { + vm.expectRevert(bytes("Only callable by owner")); + vm.prank(STRANGER); + registry.pause(); + } + + function test_CalledByOwner_success() external { + vm.startPrank(registry.owner()); + registry.pause(); + + (IAutomationV21PlusCommon.StateLegacy memory state, , , , ) = registry.getState(); + assertTrue(state.paused); + } + + function test_revertsWhen_registerUpkeepInPausedRegistry() external { + vm.startPrank(registry.owner()); + registry.pause(); + + vm.expectRevert(Registry.RegistryPaused.selector); + uint256 id = registry.registerUpkeep( + address(TARGET1), + config.maxPerformGas, + UPKEEP_ADMIN, + uint8(Trigger.CONDITION), + address(linkToken), + "", + "", + "" + ); + } + + function test_revertsWhen_transmitInPausedRegistry() external { + vm.startPrank(registry.owner()); + registry.pause(); + + _transmit(usdUpkeepID18, registry, Registry.RegistryPaused.selector); + } +} + +contract Unpause is SetUp { + function test_RevertsWhen_CalledByNonOwner() external { + vm.startPrank(registry.owner()); + registry.pause(); + + vm.expectRevert(bytes("Only callable by owner")); + vm.startPrank(STRANGER); + registry.unpause(); + } + + function test_CalledByOwner_success() external { + vm.startPrank(registry.owner()); + registry.pause(); + (IAutomationV21PlusCommon.StateLegacy memory state1, , , , ) = registry.getState(); + assertTrue(state1.paused); + + registry.unpause(); + (IAutomationV21PlusCommon.StateLegacy memory state2, , , , ) = registry.getState(); + assertFalse(state2.paused); + } +} + +contract CancelUpkeep is SetUp { + event UpkeepCanceled(uint256 indexed id, uint64 indexed atBlockHeight); + + function test_RevertsWhen_IdIsInvalid_CalledByAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + vm.expectRevert(Registry.CannotCancel.selector); + registry.cancelUpkeep(1111111); + } + + function test_RevertsWhen_IdIsInvalid_CalledByOwner() external { + vm.startPrank(registry.owner()); + vm.expectRevert(Registry.CannotCancel.selector); + registry.cancelUpkeep(1111111); + } + + function test_RevertsWhen_NotCalledByOwnerOrAdmin() external { + vm.startPrank(STRANGER); + vm.expectRevert(Registry.OnlyCallableByOwnerOrAdmin.selector); + registry.cancelUpkeep(linkUpkeepID); + } + + function test_RevertsWhen_UpkeepAlreadyCanceledByAdmin_CalledByOwner() external { + uint256 bn = block.number; + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + + vm.startPrank(registry.owner()); + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.cancelUpkeep(linkUpkeepID); + } + + function test_RevertsWhen_UpkeepAlreadyCanceledByOwner_CalledByAdmin() external { + uint256 bn = block.number; + vm.startPrank(registry.owner()); + registry.cancelUpkeep(linkUpkeepID); + + vm.startPrank(UPKEEP_ADMIN); + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.cancelUpkeep(linkUpkeepID); + } + + function test_RevertsWhen_UpkeepAlreadyCanceledByAdmin_CalledByAdmin() external { + uint256 bn = block.number; + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.cancelUpkeep(linkUpkeepID); + } + + function test_RevertsWhen_UpkeepAlreadyCanceledByOwner_CalledByOwner() external { + uint256 bn = block.number; + vm.startPrank(registry.owner()); + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.cancelUpkeep(linkUpkeepID); + } + + function test_CancelUpkeep_SetMaxValidBlockNumber_CalledByAdmin() external { + uint256 bn = block.number; + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + + uint256 maxValidBlocknumber = uint256(registry.getUpkeep(linkUpkeepID).maxValidBlocknumber); + + // 50 is the cancellation delay + assertEq(bn + 50, maxValidBlocknumber); + } + + function test_CancelUpkeep_SetMaxValidBlockNumber_CalledByOwner() external { + uint256 bn = block.number; + vm.startPrank(registry.owner()); + registry.cancelUpkeep(linkUpkeepID); + + uint256 maxValidBlocknumber = uint256(registry.getUpkeep(linkUpkeepID).maxValidBlocknumber); + + // cancellation by registry owner is immediate and no cancellation delay is applied + assertEq(bn, maxValidBlocknumber); + } + + function test_CancelUpkeep_EmitEvent_CalledByAdmin() external { + uint256 bn = block.number; + vm.startPrank(UPKEEP_ADMIN); + + vm.expectEmit(); + emit UpkeepCanceled(linkUpkeepID, uint64(bn + 50)); + registry.cancelUpkeep(linkUpkeepID); + } + + function test_CancelUpkeep_EmitEvent_CalledByOwner() external { + uint256 bn = block.number; + vm.startPrank(registry.owner()); + + vm.expectEmit(); + emit UpkeepCanceled(linkUpkeepID, uint64(bn)); + registry.cancelUpkeep(linkUpkeepID); + } +} + +contract SetPeerRegistryMigrationPermission is SetUp { + function test_SetPeerRegistryMigrationPermission_CalledByOwner() external { + address peer = randomAddress(); + vm.startPrank(registry.owner()); + + uint8 permission = registry.getPeerRegistryMigrationPermission(peer); + assertEq(0, permission); + + registry.setPeerRegistryMigrationPermission(peer, 1); + permission = registry.getPeerRegistryMigrationPermission(peer); + assertEq(1, permission); + + registry.setPeerRegistryMigrationPermission(peer, 2); + permission = registry.getPeerRegistryMigrationPermission(peer); + assertEq(2, permission); + + registry.setPeerRegistryMigrationPermission(peer, 0); + permission = registry.getPeerRegistryMigrationPermission(peer); + assertEq(0, permission); + } + + function test_RevertsWhen_InvalidPermission_CalledByOwner() external { + address peer = randomAddress(); + vm.startPrank(registry.owner()); + + vm.expectRevert(); + registry.setPeerRegistryMigrationPermission(peer, 100); + } + + function test_RevertsWhen_CalledByNonOwner() external { + address peer = randomAddress(); + vm.startPrank(STRANGER); + + vm.expectRevert(bytes("Only callable by owner")); + registry.setPeerRegistryMigrationPermission(peer, 1); + } +} + +contract SetUpkeepPrivilegeConfig is SetUp { + function test_RevertsWhen_CalledByNonManager() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByUpkeepPrivilegeManager.selector); + registry.setUpkeepPrivilegeConfig(linkUpkeepID, hex"1233"); + } + + function test_UpkeepHasEmptyConfig() external { + bytes memory cfg = registry.getUpkeepPrivilegeConfig(linkUpkeepID); + assertEq(cfg, bytes("")); + } + + function test_SetUpkeepPrivilegeConfig_CalledByManager() external { + vm.startPrank(PRIVILEGE_MANAGER); + + registry.setUpkeepPrivilegeConfig(linkUpkeepID, hex"1233"); + + bytes memory cfg = registry.getUpkeepPrivilegeConfig(linkUpkeepID); + assertEq(cfg, hex"1233"); + } +} + +contract SetAdminPrivilegeConfig is SetUp { + function test_RevertsWhen_CalledByNonManager() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByUpkeepPrivilegeManager.selector); + registry.setAdminPrivilegeConfig(randomAddress(), hex"1233"); + } + + function test_UpkeepHasEmptyConfig() external { + bytes memory cfg = registry.getAdminPrivilegeConfig(randomAddress()); + assertEq(cfg, bytes("")); + } + + function test_SetAdminPrivilegeConfig_CalledByManager() external { + vm.startPrank(PRIVILEGE_MANAGER); + address admin = randomAddress(); + + registry.setAdminPrivilegeConfig(admin, hex"1233"); + + bytes memory cfg = registry.getAdminPrivilegeConfig(admin); + assertEq(cfg, hex"1233"); + } +} + +contract TransferUpkeepAdmin is SetUp { + event UpkeepAdminTransferRequested(uint256 indexed id, address indexed from, address indexed to); + + function test_RevertsWhen_NotCalledByAdmin() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.transferUpkeepAdmin(linkUpkeepID, randomAddress()); + } + + function test_RevertsWhen_TransferToSelf() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.ValueNotChanged.selector); + registry.transferUpkeepAdmin(linkUpkeepID, UPKEEP_ADMIN); + } + + function test_RevertsWhen_UpkeepCanceled() external { + vm.startPrank(UPKEEP_ADMIN); + + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.transferUpkeepAdmin(linkUpkeepID, randomAddress()); + } + + function test_DoesNotChangeUpkeepAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + registry.transferUpkeepAdmin(linkUpkeepID, randomAddress()); + + assertEq(registry.getUpkeep(linkUpkeepID).admin, UPKEEP_ADMIN); + } + + function test_EmitEvent_CalledByAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + address newAdmin = randomAddress(); + + vm.expectEmit(); + emit UpkeepAdminTransferRequested(linkUpkeepID, UPKEEP_ADMIN, newAdmin); + registry.transferUpkeepAdmin(linkUpkeepID, newAdmin); + + // transferring to the same propose admin won't yield another event + vm.recordLogs(); + registry.transferUpkeepAdmin(linkUpkeepID, newAdmin); + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(0, entries.length); + } + + function test_CancelTransfer_ByTransferToEmptyAddress() external { + vm.startPrank(UPKEEP_ADMIN); + address newAdmin = randomAddress(); + + vm.expectEmit(); + emit UpkeepAdminTransferRequested(linkUpkeepID, UPKEEP_ADMIN, newAdmin); + registry.transferUpkeepAdmin(linkUpkeepID, newAdmin); + + vm.expectEmit(); + emit UpkeepAdminTransferRequested(linkUpkeepID, UPKEEP_ADMIN, address(0)); + registry.transferUpkeepAdmin(linkUpkeepID, address(0)); + } +} + +contract AcceptUpkeepAdmin is SetUp { + event UpkeepAdminTransferred(uint256 indexed id, address indexed from, address indexed to); + + function test_RevertsWhen_NotCalledByProposedAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + address newAdmin = randomAddress(); + registry.transferUpkeepAdmin(linkUpkeepID, newAdmin); + + vm.startPrank(STRANGER); + vm.expectRevert(Registry.OnlyCallableByProposedAdmin.selector); + registry.acceptUpkeepAdmin(linkUpkeepID); + } + + function test_RevertsWhen_UpkeepCanceled() external { + vm.startPrank(UPKEEP_ADMIN); + address newAdmin = randomAddress(); + registry.transferUpkeepAdmin(linkUpkeepID, newAdmin); + + registry.cancelUpkeep(linkUpkeepID); + + vm.startPrank(newAdmin); + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.acceptUpkeepAdmin(linkUpkeepID); + } + + function test_UpkeepAdminChanged() external { + vm.startPrank(UPKEEP_ADMIN); + address newAdmin = randomAddress(); + registry.transferUpkeepAdmin(linkUpkeepID, newAdmin); + + vm.startPrank(newAdmin); + vm.expectEmit(); + emit UpkeepAdminTransferred(linkUpkeepID, UPKEEP_ADMIN, newAdmin); + registry.acceptUpkeepAdmin(linkUpkeepID); + + assertEq(newAdmin, registry.getUpkeep(linkUpkeepID).admin); + } +} + +contract PauseUpkeep is SetUp { + event UpkeepPaused(uint256 indexed id); + + function test_RevertsWhen_NotCalledByUpkeepAdmin() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.pauseUpkeep(linkUpkeepID); + } + + function test_RevertsWhen_InvalidUpkeepId() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.pauseUpkeep(linkUpkeepID + 1); + } + + function test_RevertsWhen_UpkeepAlreadyCanceled() external { + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.pauseUpkeep(linkUpkeepID); + } + + function test_RevertsWhen_UpkeepAlreadyPaused() external { + vm.startPrank(UPKEEP_ADMIN); + registry.pauseUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.OnlyUnpausedUpkeep.selector); + registry.pauseUpkeep(linkUpkeepID); + } + + function test_EmitEvent_CalledByAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectEmit(); + emit UpkeepPaused(linkUpkeepID); + registry.pauseUpkeep(linkUpkeepID); + } +} + +contract UnpauseUpkeep is SetUp { + event UpkeepUnpaused(uint256 indexed id); + + function test_RevertsWhen_InvalidUpkeepId() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.unpauseUpkeep(linkUpkeepID + 1); + } + + function test_RevertsWhen_UpkeepIsNotPaused() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.OnlyPausedUpkeep.selector); + registry.unpauseUpkeep(linkUpkeepID); + } + + function test_RevertsWhen_UpkeepAlreadyCanceled() external { + vm.startPrank(UPKEEP_ADMIN); + registry.pauseUpkeep(linkUpkeepID); + + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.unpauseUpkeep(linkUpkeepID); + } + + function test_RevertsWhen_NotCalledByUpkeepAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + registry.pauseUpkeep(linkUpkeepID); + + vm.startPrank(STRANGER); + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.unpauseUpkeep(linkUpkeepID); + } + + function test_UnpauseUpkeep_CalledByUpkeepAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + registry.pauseUpkeep(linkUpkeepID); + + uint256[] memory ids1 = registry.getActiveUpkeepIDs(0, 0); + + vm.expectEmit(); + emit UpkeepUnpaused(linkUpkeepID); + registry.unpauseUpkeep(linkUpkeepID); + + uint256[] memory ids2 = registry.getActiveUpkeepIDs(0, 0); + assertEq(ids1.length + 1, ids2.length); + } +} + +contract SetUpkeepCheckData is SetUp { + event UpkeepCheckDataSet(uint256 indexed id, bytes newCheckData); + + function test_RevertsWhen_InvalidUpkeepId() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.setUpkeepCheckData(linkUpkeepID + 1, hex"1234"); + } + + function test_RevertsWhen_UpkeepAlreadyCanceled() external { + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.setUpkeepCheckData(linkUpkeepID, hex"1234"); + } + + function test_RevertsWhen_NewCheckDataTooLarge() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.CheckDataExceedsLimit.selector); + registry.setUpkeepCheckData(linkUpkeepID, new bytes(10_000)); + } + + function test_RevertsWhen_NotCalledByAdmin() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.setUpkeepCheckData(linkUpkeepID, hex"1234"); + } + + function test_UpdateOffchainConfig_CalledByAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectEmit(); + emit UpkeepCheckDataSet(linkUpkeepID, hex"1234"); + registry.setUpkeepCheckData(linkUpkeepID, hex"1234"); + + assertEq(registry.getUpkeep(linkUpkeepID).checkData, hex"1234"); + } + + function test_UpdateOffchainConfigOnPausedUpkeep_CalledByAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + + registry.pauseUpkeep(linkUpkeepID); + + vm.expectEmit(); + emit UpkeepCheckDataSet(linkUpkeepID, hex"1234"); + registry.setUpkeepCheckData(linkUpkeepID, hex"1234"); + + assertTrue(registry.getUpkeep(linkUpkeepID).paused); + assertEq(registry.getUpkeep(linkUpkeepID).checkData, hex"1234"); + } +} + +contract SetUpkeepGasLimit is SetUp { + event UpkeepGasLimitSet(uint256 indexed id, uint96 gasLimit); + + function test_RevertsWhen_InvalidUpkeepId() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.setUpkeepGasLimit(linkUpkeepID + 1, 1230000); + } + + function test_RevertsWhen_UpkeepAlreadyCanceled() external { + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.setUpkeepGasLimit(linkUpkeepID, 1230000); + } + + function test_RevertsWhen_NewGasLimitOutOfRange() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.GasLimitOutsideRange.selector); + registry.setUpkeepGasLimit(linkUpkeepID, 300); + + vm.expectRevert(Registry.GasLimitOutsideRange.selector); + registry.setUpkeepGasLimit(linkUpkeepID, 3000000000); + } + + function test_RevertsWhen_NotCalledByAdmin() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.setUpkeepGasLimit(linkUpkeepID, 1230000); + } + + function test_UpdateGasLimit_CalledByAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectEmit(); + emit UpkeepGasLimitSet(linkUpkeepID, 1230000); + registry.setUpkeepGasLimit(linkUpkeepID, 1230000); + + assertEq(registry.getUpkeep(linkUpkeepID).performGas, 1230000); + } +} + +contract SetUpkeepOffchainConfig is SetUp { + event UpkeepOffchainConfigSet(uint256 indexed id, bytes offchainConfig); + + function test_RevertsWhen_InvalidUpkeepId() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.setUpkeepOffchainConfig(linkUpkeepID + 1, hex"1233"); + } + + function test_RevertsWhen_UpkeepAlreadyCanceled() external { + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.setUpkeepOffchainConfig(linkUpkeepID, hex"1234"); + } + + function test_RevertsWhen_NotCalledByAdmin() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.setUpkeepOffchainConfig(linkUpkeepID, hex"1234"); + } + + function test_UpdateOffchainConfig_CalledByAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectEmit(); + emit UpkeepOffchainConfigSet(linkUpkeepID, hex"1234"); + registry.setUpkeepOffchainConfig(linkUpkeepID, hex"1234"); + + assertEq(registry.getUpkeep(linkUpkeepID).offchainConfig, hex"1234"); + } +} + +contract SetUpkeepTriggerConfig is SetUp { + event UpkeepTriggerConfigSet(uint256 indexed id, bytes triggerConfig); + + function test_RevertsWhen_InvalidUpkeepId() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.setUpkeepTriggerConfig(linkUpkeepID + 1, hex"1233"); + } + + function test_RevertsWhen_UpkeepAlreadyCanceled() external { + vm.startPrank(UPKEEP_ADMIN); + registry.cancelUpkeep(linkUpkeepID); + + vm.expectRevert(Registry.UpkeepCancelled.selector); + registry.setUpkeepTriggerConfig(linkUpkeepID, hex"1234"); + } + + function test_RevertsWhen_NotCalledByAdmin() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByAdmin.selector); + registry.setUpkeepTriggerConfig(linkUpkeepID, hex"1234"); + } + + function test_UpdateTriggerConfig_CalledByAdmin() external { + vm.startPrank(UPKEEP_ADMIN); + + vm.expectEmit(); + emit UpkeepTriggerConfigSet(linkUpkeepID, hex"1234"); + registry.setUpkeepTriggerConfig(linkUpkeepID, hex"1234"); + + assertEq(registry.getUpkeepTriggerConfig(linkUpkeepID), hex"1234"); + } +} + +contract TransferPayeeship is SetUp { + event PayeeshipTransferRequested(address indexed transmitter, address indexed from, address indexed to); + + function test_RevertsWhen_NotCalledByPayee() external { + vm.startPrank(STRANGER); + + vm.expectRevert(Registry.OnlyCallableByPayee.selector); + registry.transferPayeeship(TRANSMITTERS[0], randomAddress()); + } + + function test_RevertsWhen_TransferToSelf() external { + registry.setPayees(PAYEES); + vm.startPrank(PAYEES[0]); + + vm.expectRevert(Registry.ValueNotChanged.selector); + registry.transferPayeeship(TRANSMITTERS[0], PAYEES[0]); + } + + function test_Transfer_DoesNotChangePayee() external { + registry.setPayees(PAYEES); + + vm.startPrank(PAYEES[0]); + + registry.transferPayeeship(TRANSMITTERS[0], randomAddress()); + + (, , , , address payee) = registry.getTransmitterInfo(TRANSMITTERS[0]); + assertEq(PAYEES[0], payee); + } + + function test_EmitEvent_CalledByPayee() external { + registry.setPayees(PAYEES); + + vm.startPrank(PAYEES[0]); + address newPayee = randomAddress(); + + vm.expectEmit(); + emit PayeeshipTransferRequested(TRANSMITTERS[0], PAYEES[0], newPayee); + registry.transferPayeeship(TRANSMITTERS[0], newPayee); + + // transferring to the same propose payee won't yield another event + vm.recordLogs(); + registry.transferPayeeship(TRANSMITTERS[0], newPayee); + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(0, entries.length); + } +} + +contract AcceptPayeeship is SetUp { + event PayeeshipTransferred(address indexed transmitter, address indexed from, address indexed to); + + function test_RevertsWhen_NotCalledByProposedPayee() external { + registry.setPayees(PAYEES); + + vm.startPrank(PAYEES[0]); + address newPayee = randomAddress(); + registry.transferPayeeship(TRANSMITTERS[0], newPayee); + + vm.startPrank(STRANGER); + vm.expectRevert(Registry.OnlyCallableByProposedPayee.selector); + registry.acceptPayeeship(TRANSMITTERS[0]); + } + + function test_PayeeChanged() external { + registry.setPayees(PAYEES); + + vm.startPrank(PAYEES[0]); + address newPayee = randomAddress(); + registry.transferPayeeship(TRANSMITTERS[0], newPayee); + + vm.startPrank(newPayee); + vm.expectEmit(); + emit PayeeshipTransferred(TRANSMITTERS[0], PAYEES[0], newPayee); + registry.acceptPayeeship(TRANSMITTERS[0]); + + (, , , , address payee) = registry.getTransmitterInfo(TRANSMITTERS[0]); + assertEq(newPayee, payee); + } +} + +contract SetPayees is SetUp { + event PayeesUpdated(address[] transmitters, address[] payees); + + function test_RevertsWhen_NotCalledByOwner() external { + vm.startPrank(STRANGER); + + vm.expectRevert(bytes("Only callable by owner")); + registry.setPayees(NEW_PAYEES); + } + + function test_RevertsWhen_PayeesLengthError() external { + vm.startPrank(registry.owner()); + + address[] memory payees = new address[](5); + vm.expectRevert(Registry.ParameterLengthError.selector); + registry.setPayees(payees); + } + + function test_RevertsWhen_InvalidPayee() external { + vm.startPrank(registry.owner()); + + NEW_PAYEES[0] = address(0); + vm.expectRevert(Registry.InvalidPayee.selector); + registry.setPayees(NEW_PAYEES); + } + + function test_SetPayees_WhenExistingPayeesAreEmpty() external { + (registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN); + + for (uint256 i = 0; i < TRANSMITTERS.length; i++) { + (, , , , address payee) = registry.getTransmitterInfo(TRANSMITTERS[i]); + assertEq(address(0), payee); + } + + vm.startPrank(registry.owner()); + + vm.expectEmit(); + emit PayeesUpdated(TRANSMITTERS, PAYEES); + registry.setPayees(PAYEES); + for (uint256 i = 0; i < TRANSMITTERS.length; i++) { + (bool active, , , , address payee) = registry.getTransmitterInfo(TRANSMITTERS[i]); + assertTrue(active); + assertEq(PAYEES[i], payee); + } + } + + function test_DotNotSetPayeesToIgnoredAddress() external { + address IGNORE_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + (registry, ) = deployAndConfigureZKSyncRegistryAndRegistrar(AutoBase.PayoutMode.ON_CHAIN); + PAYEES[0] = IGNORE_ADDRESS; + + registry.setPayees(PAYEES); + (bool active, , , , address payee) = registry.getTransmitterInfo(TRANSMITTERS[0]); + assertTrue(active); + assertEq(address(0), payee); + + (active, , , , payee) = registry.getTransmitterInfo(TRANSMITTERS[1]); + assertTrue(active); + assertEq(PAYEES[1], payee); + } +} + +contract GetActiveUpkeepIDs is SetUp { + function test_RevertsWhen_IndexOutOfRange() external { + vm.expectRevert(Registry.IndexOutOfRange.selector); + registry.getActiveUpkeepIDs(5, 0); + + vm.expectRevert(Registry.IndexOutOfRange.selector); + registry.getActiveUpkeepIDs(6, 0); + } + + function test_ReturnsAllUpkeeps_WhenMaxCountIsZero() external { + uint256[] memory uids = registry.getActiveUpkeepIDs(0, 0); + assertEq(5, uids.length); + + uids = registry.getActiveUpkeepIDs(2, 0); + assertEq(3, uids.length); + } + + function test_ReturnsAllRemainingUpkeeps_WhenMaxCountIsTooLarge() external { + uint256[] memory uids = registry.getActiveUpkeepIDs(2, 20); + assertEq(3, uids.length); + } + + function test_ReturnsUpkeeps_BoundByMaxCount() external { + uint256[] memory uids = registry.getActiveUpkeepIDs(1, 2); + assertEq(2, uids.length); + assertEq(uids[0], linkUpkeepID2); + assertEq(uids[1], usdUpkeepID18); + } +} diff --git a/contracts/src/v0.8/automation/testhelpers/UpkeepCounterNew.sol b/contracts/src/v0.8/automation/testhelpers/UpkeepCounterNew.sol new file mode 100644 index 00000000000..76b38776893 --- /dev/null +++ b/contracts/src/v0.8/automation/testhelpers/UpkeepCounterNew.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +contract UpkeepCounterNew { + event PerformingUpkeep( + address indexed from, + uint256 initialTimestamp, + uint256 lastTimestamp, + uint256 previousBlock, + uint256 counter + ); + error InvalidCaller(address caller, address forwarder); + + uint256 public testRange; + uint256 public interval; + uint256 public lastTimestamp; + uint256 public previousPerformBlock; + uint256 public initialTimestamp; + uint256 public counter; + bool public useMoreCheckGas; + bool public useMorePerformGas; + bool public useMorePerformData; + uint256 public checkGasToBurn; + uint256 public performGasToBurn; + bytes public data; + bytes public dataCopy; + bool public trickSimulation = false; + address public forwarder; + + constructor() { + testRange = 1000000; + interval = 40; + previousPerformBlock = 0; + lastTimestamp = block.timestamp; + initialTimestamp = 0; + counter = 0; + useMoreCheckGas = false; + useMorePerformData = false; + useMorePerformGas = false; + checkGasToBurn = 9700000; + performGasToBurn = 7700000; + } + + function setPerformGasToBurn(uint256 _performGasToBurn) external { + performGasToBurn = _performGasToBurn; + } + + function setCheckGasToBurn(uint256 _checkGasToBurn) external { + checkGasToBurn = _checkGasToBurn; + } + + function setUseMoreCheckGas(bool _useMoreCheckGas) external { + useMoreCheckGas = _useMoreCheckGas; + } + + function setUseMorePerformGas(bool _useMorePerformGas) external { + useMorePerformGas = _useMorePerformGas; + } + + function setUseMorePerformData(bool _useMorePerformData) external { + useMorePerformData = _useMorePerformData; + } + + function setData(bytes calldata _data) external { + data = _data; + } + + function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) { + if (useMoreCheckGas) { + uint256 startGas = gasleft(); + while (startGas - gasleft() < checkGasToBurn) {} // burn gas + } + + if (useMorePerformData) { + return (eligible(), data); + } + return (eligible(), ""); + } + + function setTrickSimulation(bool _trickSimulation) external { + trickSimulation = _trickSimulation; + } + + function performUpkeep(bytes calldata performData) external { + if (trickSimulation && tx.origin == address(0)) { + return; + } + + if (msg.sender != forwarder) { + revert InvalidCaller(msg.sender, forwarder); + } + + if (useMorePerformGas) { + uint256 startGas = gasleft(); + while (startGas - gasleft() < performGasToBurn) {} // burn gas + } + + if (initialTimestamp == 0) { + initialTimestamp = block.timestamp; + } + lastTimestamp = block.timestamp; + counter = counter + 1; + dataCopy = performData; + emit PerformingUpkeep(tx.origin, initialTimestamp, lastTimestamp, previousPerformBlock, counter); + previousPerformBlock = lastTimestamp; + } + + function eligible() public view returns (bool) { + if (initialTimestamp == 0) { + return true; + } + + return (block.timestamp - initialTimestamp) < testRange && (block.timestamp - lastTimestamp) >= interval; + } + + function setSpread(uint256 _testRange, uint256 _interval) external { + testRange = _testRange; + interval = _interval; + initialTimestamp = 0; + counter = 0; + } + + function setForwarder(address _forwarder) external { + forwarder = _forwarder; + } +} diff --git a/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistry2_3.sol b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistry2_3.sol index 00858085e3e..67a9a56b3d5 100644 --- a/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistry2_3.sol +++ b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistry2_3.sol @@ -72,7 +72,6 @@ contract ZKSyncAutomationRegistry2_3 is ZKSyncAutomationRegistryBase2_3, OCR2Abs uint16 numUpkeepsPassedChecks; uint96 totalReimbursement; uint96 totalPremium; - uint256 totalCalldataWeight; } // ================================================================ @@ -89,7 +88,6 @@ contract ZKSyncAutomationRegistry2_3 is ZKSyncAutomationRegistryBase2_3, OCR2Abs bytes32[] calldata ss, bytes32 rawVs ) external override { - uint256 gasOverhead = gasleft(); // use this msg.data length check to ensure no extra data is included in the call // 4 is first 4 bytes of the keccak-256 hash of the function signature. ss.length == rs.length so use one of them // 4 + (32 * 3) + (rawReport.length + 32 + 32) + (32 * rs.length + 32 + 32) + (32 * ss.length + 32 + 32) + 32 @@ -110,7 +108,7 @@ contract ZKSyncAutomationRegistry2_3 is ZKSyncAutomationRegistryBase2_3, OCR2Abs uint40 epochAndRound = uint40(uint256(reportContext[1])); uint32 epoch = uint32(epochAndRound >> 8); - _handleReport(hotVars, report, gasOverhead); + _handleReport(hotVars, report); if (epoch > hotVars.latestEpoch) { s_hotVars.latestEpoch = epoch; @@ -121,22 +119,20 @@ contract ZKSyncAutomationRegistry2_3 is ZKSyncAutomationRegistryBase2_3, OCR2Abs * @notice handles the report by performing the upkeeps and updating the state * @param hotVars the hot variables of the registry * @param report the report to be handled (already verified and decoded) - * @param gasOverhead the running tally of gas overhead to be split across the upkeeps * @dev had to split this function from transmit() to avoid stack too deep errors * @dev all other internal / private functions are generally defined in the Base contract * we leave this here because it is essentially a continuation of the transmit() function, */ - function _handleReport(HotVars memory hotVars, Report memory report, uint256 gasOverhead) private { + function _handleReport(HotVars memory hotVars, Report memory report) private { UpkeepTransmitInfo[] memory upkeepTransmitInfo = new UpkeepTransmitInfo[](report.upkeepIds.length); TransmitVars memory transmitVars = TransmitVars({ numUpkeepsPassedChecks: 0, - totalCalldataWeight: 0, totalReimbursement: 0, totalPremium: 0 }); uint256 blocknumber = hotVars.chainModule.blockNumber(); - uint256 l1Fee = hotVars.chainModule.getCurrentL1Fee(msg.data.length); // this will be updated + uint256 gasOverhead; for (uint256 i = 0; i < report.upkeepIds.length; i++) { upkeepTransmitInfo[i].upkeep = s_upkeep[report.upkeepIds[i]]; @@ -163,28 +159,25 @@ contract ZKSyncAutomationRegistry2_3 is ZKSyncAutomationRegistryBase2_3, OCR2Abs report.performDatas[i] ); - // To split L1 fee across the upkeeps, assign a weight to this upkeep based on the length - // of the perform data and calldata overhead - upkeepTransmitInfo[i].calldataWeight = - report.performDatas[i].length + - TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD + - (TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD * (hotVars.f + 1)); - transmitVars.totalCalldataWeight += upkeepTransmitInfo[i].calldataWeight; - - // Deduct the gasUsed by upkeep from the overhead tally - upkeeps pay for their own gas individually - gasOverhead -= upkeepTransmitInfo[i].gasUsed; - // Store last perform block number / deduping key for upkeep _updateTriggerMarker(report.upkeepIds[i], blocknumber, upkeepTransmitInfo[i]); + + if (upkeepTransmitInfo[i].triggerType == Trigger.CONDITION) { + gasOverhead += REGISTRY_CONDITIONAL_OVERHEAD; + } else { + gasOverhead += REGISTRY_LOG_OVERHEAD; + } } // No upkeeps to be performed in this report if (transmitVars.numUpkeepsPassedChecks == 0) { return; } - // This is the overall gas overhead that will be split across performed upkeeps - // Take upper bound of 16 gas per callData bytes - gasOverhead = (gasOverhead - gasleft()) + (16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD; + gasOverhead += + 16 * + msg.data.length + + ACCOUNTING_FIXED_GAS_OVERHEAD + + (REGISTRY_PER_SIGNER_GAS_OVERHEAD * (hotVars.f + 1)); gasOverhead = gasOverhead / transmitVars.numUpkeepsPassedChecks + ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD; { @@ -200,7 +193,7 @@ contract ZKSyncAutomationRegistry2_3 is ZKSyncAutomationRegistryBase2_3, OCR2Abs PaymentParams({ gasLimit: upkeepTransmitInfo[i].gasUsed, gasOverhead: gasOverhead, - l1CostWei: (l1Fee * upkeepTransmitInfo[i].calldataWeight) / transmitVars.totalCalldataWeight, + l1CostWei: 0, fastGasWei: report.fastGasWei, linkUSD: report.linkUSD, nativeUSD: nativeUSD, diff --git a/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryBase2_3.sol b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryBase2_3.sol index 524ecacc826..41097af7f26 100644 --- a/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryBase2_3.sol +++ b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryBase2_3.sol @@ -34,8 +34,6 @@ abstract contract ZKSyncAutomationRegistryBase2_3 is ConfirmedOwner { bytes4 internal constant CHECK_LOG_SELECTOR = ILogAutomation.checkLog.selector; uint256 internal constant PERFORM_GAS_MIN = 2_300; uint256 internal constant CANCELLATION_DELAY = 50; - uint256 internal constant PERFORM_GAS_CUSHION = 5_000; - uint256 internal constant PPB_BASE = 1_000_000_000; uint32 internal constant UINT32_MAX = type(uint32).max; // The first byte of the mask can be 0, because we only ever have 31 oracles uint256 internal constant ORACLE_MASK = 0x0001010101010101010101010101010101010101010101010101010101010101; @@ -47,20 +45,13 @@ abstract contract ZKSyncAutomationRegistryBase2_3 is ConfirmedOwner { uint256 internal constant REGISTRY_CONDITIONAL_OVERHEAD = 98_200; // Fixed gas overhead for conditional upkeeps uint256 internal constant REGISTRY_LOG_OVERHEAD = 122_500; // Fixed gas overhead for log upkeeps uint256 internal constant REGISTRY_PER_SIGNER_GAS_OVERHEAD = 5_600; // Value scales with f - uint256 internal constant REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD = 24; // Per perform data byte overhead - - // The overhead (in bytes) in addition to perform data for upkeep sent in calldata - // This includes overhead for all struct encoding as well as report signatures - // There is a fixed component and a per signer component. This is calculated exactly by doing abi encoding - uint256 internal constant TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD = 932; - uint256 internal constant TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD = 64; // Next block of constants are used in actual payment calculation. We calculate the exact gas used within the // tx itself, but since payment processing itself takes gas, and it needs the overhead as input, we use fixed constants // to account for gas used in payment processing. These values are calibrated using hardhat tests which simulates various cases and verifies that // the variables result in accurate estimation - uint256 internal constant ACCOUNTING_FIXED_GAS_OVERHEAD = 51_200; // Fixed overhead per tx - uint256 internal constant ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD = 14_200; // Overhead per upkeep performed in batch + uint256 internal constant ACCOUNTING_FIXED_GAS_OVERHEAD = 51_000; // Fixed overhead per tx + uint256 internal constant ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD = 20_000; // Overhead per upkeep performed in batch LinkTokenInterface internal immutable i_link; AggregatorV3Interface internal immutable i_linkUSDFeed; @@ -313,7 +304,6 @@ abstract contract ZKSyncAutomationRegistryBase2_3 is ConfirmedOwner { * @member performSuccess whether the perform was successful * @member triggerType the type of trigger * @member gasUsed gasUsed by this upkeep in perform - * @member calldataWeight weight assigned to this upkeep for its contribution to calldata. It is used to split L1 fee * @member dedupID unique ID used to dedup an upkeep/trigger combo */ struct UpkeepTransmitInfo { @@ -322,7 +312,6 @@ abstract contract ZKSyncAutomationRegistryBase2_3 is ConfirmedOwner { bool performSuccess; Trigger triggerType; uint256 gasUsed; - uint256 calldataWeight; bytes32 dedupID; } @@ -739,7 +728,6 @@ abstract contract ZKSyncAutomationRegistryBase2_3 is ConfirmedOwner { uint256 nativeUSD, IERC20 billingToken ) internal view returns (uint96) { - uint256 maxL1Fee; uint256 maxGasOverhead; { @@ -750,15 +738,8 @@ abstract contract ZKSyncAutomationRegistryBase2_3 is ConfirmedOwner { } else { revert InvalidTriggerType(); } - uint256 maxCalldataSize = s_storage.maxPerformDataSize + - TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD + - (TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD * (hotVars.f + 1)); - (uint256 chainModuleFixedOverhead, uint256 chainModulePerByteOverhead) = s_hotVars.chainModule.getGasOverhead(); - maxGasOverhead += - (REGISTRY_PER_SIGNER_GAS_OVERHEAD * (hotVars.f + 1)) + - ((REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD + chainModulePerByteOverhead) * maxCalldataSize) + - chainModuleFixedOverhead; - maxL1Fee = hotVars.gasCeilingMultiplier * hotVars.chainModule.getMaxL1Fee(maxCalldataSize); + (uint256 chainModuleFixedOverhead, ) = s_hotVars.chainModule.getGasOverhead(); + maxGasOverhead += (REGISTRY_PER_SIGNER_GAS_OVERHEAD * (hotVars.f + 1)) + chainModuleFixedOverhead; } BillingTokenPaymentParams memory paymentParams = _getBillingTokenPaymentParams(hotVars, billingToken); @@ -774,7 +755,7 @@ abstract contract ZKSyncAutomationRegistryBase2_3 is ConfirmedOwner { PaymentParams({ gasLimit: performGas, gasOverhead: maxGasOverhead, - l1CostWei: maxL1Fee, + l1CostWei: 0, fastGasWei: fastGasWei, linkUSD: linkUSD, nativeUSD: nativeUSD, diff --git a/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicC2_3.sol b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicC2_3.sol index 61d0eecfbaf..3b4b023c7a2 100644 --- a/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicC2_3.sol +++ b/contracts/src/v0.8/automation/v2_3_zksync/ZKSyncAutomationRegistryLogicC2_3.sol @@ -222,22 +222,10 @@ contract ZKSyncAutomationRegistryLogicC2_3 is ZKSyncAutomationRegistryBase2_3 { return REGISTRY_LOG_OVERHEAD; } - function getPerPerformByteGasOverhead() external pure returns (uint256) { - return REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD; - } - function getPerSignerGasOverhead() external pure returns (uint256) { return REGISTRY_PER_SIGNER_GAS_OVERHEAD; } - function getTransmitCalldataFixedBytesOverhead() external pure returns (uint256) { - return TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD; - } - - function getTransmitCalldataPerSignerBytesOverhead() external pure returns (uint256) { - return TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD; - } - function getCancellationDelay() external pure returns (uint256) { return CANCELLATION_DELAY; } diff --git a/contracts/src/v0.8/tests/MockGasBoundCaller.sol b/contracts/src/v0.8/tests/MockGasBoundCaller.sol new file mode 100644 index 00000000000..3184f9dba38 --- /dev/null +++ b/contracts/src/v0.8/tests/MockGasBoundCaller.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +contract MockGasBoundCaller { + error TransactionFailed(address target); + + function gasBoundCall(address target, uint256 gasAmount, bytes memory data) external payable { + bool success; + assembly { + success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0) + } + + // gas bound caller will propagate the revert + if (!success) { + revert TransactionFailed(target); + } + + uint256 pubdataGas = 500000; + bytes memory returnData = abi.encode(address(0), pubdataGas); + + uint256 paddedReturndataLen = returnData.length + 96; + if (paddedReturndataLen % 32 != 0) { + paddedReturndataLen += 32 - (paddedReturndataLen % 32); + } + + assembly { + mstore(sub(returnData, 0x40), 0x40) + mstore(sub(returnData, 0x20), pubdataGas) + return(sub(returnData, 0x40), paddedReturndataLen) + } + } +} diff --git a/contracts/src/v0.8/tests/MockOVMGasPriceOracle.sol b/contracts/src/v0.8/tests/MockOVMGasPriceOracle.sol index 29790b0e150..6bb0dae6455 100644 --- a/contracts/src/v0.8/tests/MockOVMGasPriceOracle.sol +++ b/contracts/src/v0.8/tests/MockOVMGasPriceOracle.sol @@ -1,7 +1,12 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.8.6; contract MockOVMGasPriceOracle { - function getL1Fee(bytes memory _data) public view returns (uint256) { + function getL1Fee(bytes memory) public pure returns (uint256) { + return 2000000; + } + + function getL1FeeUpperBound(uint256) public pure returns (uint256) { return 2000000; } } diff --git a/contracts/src/v0.8/tests/MockZKSyncSystemContext.sol b/contracts/src/v0.8/tests/MockZKSyncSystemContext.sol new file mode 100644 index 00000000000..265d4b678a5 --- /dev/null +++ b/contracts/src/v0.8/tests/MockZKSyncSystemContext.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +contract MockZKSyncSystemContext { + function gasPrice() external pure returns (uint256) { + return 250000000; // 0.25 gwei + } + + function gasPerPubdataByte() external pure returns (uint256) { + return 500; + } + + function getCurrentPubdataSpent() external pure returns (uint256 currentPubdataSpent) { + return 1000; + } +} diff --git a/contracts/test/v0.8/automation/AutomationRegistry2_2.test.ts b/contracts/test/v0.8/automation/AutomationRegistry2_2.test.ts index 62733bcad43..6b220f2f7cb 100644 --- a/contracts/test/v0.8/automation/AutomationRegistry2_2.test.ts +++ b/contracts/test/v0.8/automation/AutomationRegistry2_2.test.ts @@ -23,7 +23,7 @@ import { MockArbGasInfo__factory as MockArbGasInfoFactory } from '../../../typec import { MockOVMGasPriceOracle__factory as MockOVMGasPriceOracleFactory } from '../../../typechain/factories/MockOVMGasPriceOracle__factory' import { ChainModuleBase__factory as ChainModuleBaseFactory } from '../../../typechain/factories/ChainModuleBase__factory' import { ArbitrumModule__factory as ArbitrumModuleFactory } from '../../../typechain/factories/ArbitrumModule__factory' -import { OptimismModule__factory as OptimismModuleFactory } from '../../../typechain/factories/OptimismModule__factory' +import { OptimismModuleV2__factory as OptimismModuleV2Factory } from '../../../typechain/factories/OptimismModuleV2__factory' import { ILogAutomation__factory as ILogAutomationactory } from '../../../typechain/factories/ILogAutomation__factory' import { IAutomationForwarder__factory as IAutomationForwarderFactory } from '../../../typechain/factories/IAutomationForwarder__factory' import { MockArbSys__factory as MockArbSysFactory } from '../../../typechain/factories/MockArbSys__factory' @@ -35,7 +35,7 @@ import { MockV3Aggregator } from '../../../typechain/MockV3Aggregator' import { UpkeepMock } from '../../../typechain/UpkeepMock' import { ChainModuleBase } from '../../../typechain/ChainModuleBase' import { ArbitrumModule } from '../../../typechain/ArbitrumModule' -import { OptimismModule } from '../../../typechain/OptimismModule' +import { OptimismModuleV2 } from '../../../typechain/OptimismModuleV2' import { UpkeepTranscoder } from '../../../typechain/UpkeepTranscoder' import { IChainModule, UpkeepAutoFunder } from '../../../typechain' import { @@ -148,7 +148,7 @@ let upkeepMockFactory: UpkeepMockFactory let upkeepAutoFunderFactory: UpkeepAutoFunderFactory let chainModuleBaseFactory: ChainModuleBaseFactory let arbitrumModuleFactory: ArbitrumModuleFactory -let optimismModuleFactory: OptimismModuleFactory +let optimismModuleV2Factory: OptimismModuleV2Factory let streamsLookupUpkeepFactory: StreamsLookupUpkeepFactory let personas: Personas @@ -169,7 +169,7 @@ let ltUpkeep: MockContract let transcoder: UpkeepTranscoder let chainModuleBase: ChainModuleBase let arbitrumModule: ArbitrumModule -let optimismModule: OptimismModule +let optimismModule: OptimismModuleV2 let streamsLookupUpkeep: StreamsLookupUpkeep let automationUtils: AutomationCompatibleUtils @@ -430,7 +430,8 @@ describe('AutomationRegistry2_2', () => { await ethers.getContractFactory('UpkeepAutoFunder') chainModuleBaseFactory = await ethers.getContractFactory('ChainModuleBase') arbitrumModuleFactory = await ethers.getContractFactory('ArbitrumModule') - optimismModuleFactory = await ethers.getContractFactory('OptimismModule') + optimismModuleV2Factory = + await ethers.getContractFactory('OptimismModuleV2') streamsLookupUpkeepFactory = await ethers.getContractFactory( 'StreamsLookupUpkeep', ) @@ -844,7 +845,7 @@ describe('AutomationRegistry2_2', () => { .deploy() chainModuleBase = await chainModuleBaseFactory.connect(owner).deploy() arbitrumModule = await arbitrumModuleFactory.connect(owner).deploy() - optimismModule = await optimismModuleFactory.connect(owner).deploy() + optimismModule = await optimismModuleV2Factory.connect(owner).deploy() streamsLookupUpkeep = await streamsLookupUpkeepFactory .connect(owner) .deploy( diff --git a/contracts/test/v0.8/automation/AutomationRegistry2_3.test.ts b/contracts/test/v0.8/automation/AutomationRegistry2_3.test.ts index 3f28a4410b1..f3c2d9bb984 100644 --- a/contracts/test/v0.8/automation/AutomationRegistry2_3.test.ts +++ b/contracts/test/v0.8/automation/AutomationRegistry2_3.test.ts @@ -23,7 +23,7 @@ import { MockArbGasInfo__factory as MockArbGasInfoFactory } from '../../../typec import { MockOVMGasPriceOracle__factory as MockOVMGasPriceOracleFactory } from '../../../typechain/factories/MockOVMGasPriceOracle__factory' import { ChainModuleBase__factory as ChainModuleBaseFactory } from '../../../typechain/factories/ChainModuleBase__factory' import { ArbitrumModule__factory as ArbitrumModuleFactory } from '../../../typechain/factories/ArbitrumModule__factory' -import { OptimismModule__factory as OptimismModuleFactory } from '../../../typechain/factories/OptimismModule__factory' +import { OptimismModuleV2__factory as OptimismModuleV2Factory } from '../../../typechain/factories/OptimismModuleV2__factory' import { ILogAutomation__factory as ILogAutomationactory } from '../../../typechain/factories/ILogAutomation__factory' import { MockArbSys__factory as MockArbSysFactory } from '../../../typechain/factories/MockArbSys__factory' import { AutomationCompatibleUtils } from '../../../typechain/AutomationCompatibleUtils' @@ -34,7 +34,7 @@ import { MockV3Aggregator } from '../../../typechain/MockV3Aggregator' import { UpkeepMock } from '../../../typechain/UpkeepMock' import { ChainModuleBase } from '../../../typechain/ChainModuleBase' import { ArbitrumModule } from '../../../typechain/ArbitrumModule' -import { OptimismModule } from '../../../typechain/OptimismModule' +import { OptimismModuleV2 } from '../../../typechain/OptimismModuleV2' import { UpkeepTranscoder } from '../../../typechain/UpkeepTranscoder' import { IChainModule, UpkeepAutoFunder } from '../../../typechain' import { @@ -152,7 +152,7 @@ let upkeepMockFactory: UpkeepMockFactory let upkeepAutoFunderFactory: UpkeepAutoFunderFactory let chainModuleBaseFactory: ChainModuleBaseFactory let arbitrumModuleFactory: ArbitrumModuleFactory -let optimismModuleFactory: OptimismModuleFactory +let optimismModuleFactory: OptimismModuleV2Factory let streamsLookupUpkeepFactory: StreamsLookupUpkeepFactory let personas: Personas @@ -174,7 +174,7 @@ let ltUpkeep: MockContract let transcoder: UpkeepTranscoder let chainModuleBase: ChainModuleBase let arbitrumModule: ArbitrumModule -let optimismModule: OptimismModule +let optimismModule: OptimismModuleV2 let streamsLookupUpkeep: StreamsLookupUpkeep let automationUtils: AutomationCompatibleUtils let automationUtils2_3: AutomationUtils2_3 @@ -442,7 +442,7 @@ describe('AutomationRegistry2_3', () => { await ethers.getContractFactory('UpkeepAutoFunder') chainModuleBaseFactory = await ethers.getContractFactory('ChainModuleBase') arbitrumModuleFactory = await ethers.getContractFactory('ArbitrumModule') - optimismModuleFactory = await ethers.getContractFactory('OptimismModule') + optimismModuleFactory = await ethers.getContractFactory('OptimismModuleV2') streamsLookupUpkeepFactory = await ethers.getContractFactory( 'StreamsLookupUpkeep', ) diff --git a/contracts/test/v0.8/automation/ZKSyncAutomationRegistry2_3.test.ts b/contracts/test/v0.8/automation/ZKSyncAutomationRegistry2_3.test.ts new file mode 100644 index 00000000000..95210cf6444 --- /dev/null +++ b/contracts/test/v0.8/automation/ZKSyncAutomationRegistry2_3.test.ts @@ -0,0 +1,4403 @@ +import { ethers } from 'hardhat' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' +import { assert, expect } from 'chai' +import { + BigNumber, + BigNumberish, + BytesLike, + Contract, + ContractFactory, + ContractReceipt, + ContractTransaction, + Signer, + Wallet, +} from 'ethers' +import { evmRevert, evmRevertCustomError } from '../../test-helpers/matchers' +import { getUsers, Personas } from '../../test-helpers/setup' +import { randomAddress, toWei } from '../../test-helpers/helpers' +import { StreamsLookupUpkeep__factory as StreamsLookupUpkeepFactory } from '../../../typechain/factories/StreamsLookupUpkeep__factory' +import { MockV3Aggregator__factory as MockV3AggregatorFactory } from '../../../typechain/factories/MockV3Aggregator__factory' +import { UpkeepMock__factory as UpkeepMockFactory } from '../../../typechain/factories/UpkeepMock__factory' +import { UpkeepAutoFunder__factory as UpkeepAutoFunderFactory } from '../../../typechain/factories/UpkeepAutoFunder__factory' +import { MockZKSyncSystemContext__factory as MockZKSyncSystemContextFactory } from '../../../typechain/factories/MockZKSyncSystemContext__factory' +import { ChainModuleBase__factory as ChainModuleBaseFactory } from '../../../typechain/factories/ChainModuleBase__factory' +import { MockGasBoundCaller__factory as MockGasBoundCallerFactory } from '../../../typechain/factories/MockGasBoundCaller__factory' +import { ILogAutomation__factory as ILogAutomationactory } from '../../../typechain/factories/ILogAutomation__factory' +import { AutomationCompatibleUtils } from '../../../typechain/AutomationCompatibleUtils' +import { StreamsLookupUpkeep } from '../../../typechain/StreamsLookupUpkeep' +import { MockV3Aggregator } from '../../../typechain/MockV3Aggregator' +import { MockGasBoundCaller } from '../../../typechain/MockGasBoundCaller' +import { UpkeepMock } from '../../../typechain/UpkeepMock' +import { ChainModuleBase } from '../../../typechain/ChainModuleBase' +import { UpkeepTranscoder } from '../../../typechain/UpkeepTranscoder' +import { MockZKSyncSystemContext } from '../../../typechain/MockZKSyncSystemContext' +import { IChainModule, UpkeepAutoFunder } from '../../../typechain' +import { + CancelledUpkeepReportEvent, + IAutomationRegistryMaster2_3 as IAutomationRegistry, + ReorgedUpkeepReportEvent, + StaleUpkeepReportEvent, + UpkeepPerformedEvent, +} from '../../../typechain/IAutomationRegistryMaster2_3' +import { + deployMockContract, + MockContract, +} from '@ethereum-waffle/mock-contract' +import { deployZKSyncRegistry23 } from './helpers' +import { AutomationUtils2_3 } from '../../../typechain/AutomationUtils2_3' + +const describeMaybe = process.env.SKIP_SLOW ? describe.skip : describe +const itMaybe = process.env.SKIP_SLOW ? it.skip : it + +// copied from AutomationRegistryInterface2_3.sol +enum UpkeepFailureReason { + NONE, + UPKEEP_CANCELLED, + UPKEEP_PAUSED, + TARGET_CHECK_REVERTED, + UPKEEP_NOT_NEEDED, + PERFORM_DATA_EXCEEDS_LIMIT, + INSUFFICIENT_BALANCE, + CHECK_CALLBACK_REVERTED, + REVERT_DATA_EXCEEDS_LIMIT, + REGISTRY_PAUSED, +} + +// copied from AutomationRegistryBase2_3.sol +enum Trigger { + CONDITION, + LOG, +} + +// un-exported types that must be extracted from the utils contract +type Report = Parameters[0] +type LogTrigger = Parameters[0] +type ConditionalTrigger = Parameters< + AutomationCompatibleUtils['_conditionalTrigger'] +>[0] +type Log = Parameters[0] +type OnChainConfig = Parameters[3] + +// ----------------------------------------------------------------------------------------------- + +// These values should match the constants declared in registry +let registryConditionalOverhead: BigNumber +let registryLogOverhead: BigNumber +let registryPerSignerGasOverhead: BigNumber +// let registryPerPerformByteGasOverhead: BigNumber +// let registryTransmitCalldataFixedBytesOverhead: BigNumber +// let registryTransmitCalldataPerSignerBytesOverhead: BigNumber +let cancellationDelay: number + +// This is the margin for gas that we test for. Gas charged should always be greater +// than total gas used in tx but should not increase beyond this margin +// const gasCalculationMargin = BigNumber.from(50_000) +// This is the margin for gas overhead estimation in checkUpkeep. The estimated gas +// overhead should be larger than actual gas overhead but should not increase beyond this margin +// const gasEstimationMargin = BigNumber.from(50_000) + +// 1 Link = 0.005 Eth +const linkUSD = BigNumber.from('2000000000') // 1 LINK = $20 +const nativeUSD = BigNumber.from('400000000000') // 1 ETH = $4000 +const gasWei = BigNumber.from(1000000000) // 1 gwei +// ----------------------------------------------------------------------------------------------- +// test-wide configs for upkeeps +const performGas = BigNumber.from('1000000') +const paymentPremiumBase = BigNumber.from('1000000000') +const paymentPremiumPPB = BigNumber.from('250000000') +const flatFeeMilliCents = BigNumber.from(0) + +const randomBytes = '0x1234abcd' +const emptyBytes = '0x' +const emptyBytes32 = + '0x0000000000000000000000000000000000000000000000000000000000000000' + +const pubdataGas = BigNumber.from(500000) +const transmitGasOverhead = 1_040_000 +const checkGasOverhead = 600_000 + +const stalenessSeconds = BigNumber.from(43820) +const gasCeilingMultiplier = BigNumber.from(2) +const checkGasLimit = BigNumber.from(10000000) +const fallbackGasPrice = gasWei.mul(BigNumber.from('2')) +const fallbackLinkPrice = linkUSD.div(BigNumber.from('2')) +const fallbackNativePrice = nativeUSD.div(BigNumber.from('2')) +const maxCheckDataSize = BigNumber.from(1000) +const maxPerformDataSize = BigNumber.from(1000) +const maxRevertDataSize = BigNumber.from(1000) +const maxPerformGas = BigNumber.from(5000000) +const minUpkeepSpend = BigNumber.from(0) +const f = 1 +const offchainVersion = 1 +const offchainBytes = '0x' +const zeroAddress = ethers.constants.AddressZero +const wrappedNativeTokenAddress = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' +const epochAndRound5_1 = + '0x0000000000000000000000000000000000000000000000000000000000000501' + +let logTriggerConfig: string + +// ----------------------------------------------------------------------------------------------- + +// Smart contract factories +let linkTokenFactory: ContractFactory +let mockV3AggregatorFactory: MockV3AggregatorFactory +let mockGasBoundCallerFactory: MockGasBoundCallerFactory +let upkeepMockFactory: UpkeepMockFactory +let upkeepAutoFunderFactory: UpkeepAutoFunderFactory +let moduleBaseFactory: ChainModuleBaseFactory +let mockZKSyncSystemContextFactory: MockZKSyncSystemContextFactory +let streamsLookupUpkeepFactory: StreamsLookupUpkeepFactory +let personas: Personas + +// contracts +let linkToken: Contract +let linkUSDFeed: MockV3Aggregator +let nativeUSDFeed: MockV3Aggregator +let gasPriceFeed: MockV3Aggregator +let registry: IAutomationRegistry // default registry, used for most tests +let mgRegistry: IAutomationRegistry // "migrate registry" used in migration tests +let mock: UpkeepMock +let autoFunderUpkeep: UpkeepAutoFunder +let ltUpkeep: MockContract +let transcoder: UpkeepTranscoder +let moduleBase: ChainModuleBase +let mockGasBoundCaller: MockGasBoundCaller +let mockZKSyncSystemContext: MockZKSyncSystemContext +let streamsLookupUpkeep: StreamsLookupUpkeep +let automationUtils: AutomationCompatibleUtils +let automationUtils2_3: AutomationUtils2_3 + +function now() { + return Math.floor(Date.now() / 1000) +} + +async function getUpkeepID(tx: ContractTransaction): Promise { + const receipt = await tx.wait() + for (const event of receipt.events || []) { + if ( + event.args && + event.eventSignature == 'UpkeepRegistered(uint256,uint32,address)' + ) { + return event.args[0] + } + } + throw new Error('could not find upkeep ID in tx event logs') +} + +const getTriggerType = (upkeepId: BigNumber): Trigger => { + const hexBytes = ethers.utils.defaultAbiCoder.encode(['uint256'], [upkeepId]) + const bytes = ethers.utils.arrayify(hexBytes) + for (let idx = 4; idx < 15; idx++) { + if (bytes[idx] != 0) { + return Trigger.CONDITION + } + } + return bytes[15] as Trigger +} + +const encodeBlockTrigger = (conditionalTrigger: ConditionalTrigger) => { + return ( + '0x' + + automationUtils.interface + .encodeFunctionData('_conditionalTrigger', [conditionalTrigger]) + .slice(10) + ) +} + +const encodeLogTrigger = (logTrigger: LogTrigger) => { + return ( + '0x' + + automationUtils.interface + .encodeFunctionData('_logTrigger', [logTrigger]) + .slice(10) + ) +} + +const encodeLog = (log: Log) => { + return ( + '0x' + automationUtils.interface.encodeFunctionData('_log', [log]).slice(10) + ) +} + +const encodeReport = (report: Report) => { + return ( + '0x' + + automationUtils2_3.interface + .encodeFunctionData('_report', [report]) + .slice(10) + ) +} + +type UpkeepData = { + Id: BigNumberish + performGas: BigNumberish + performData: BytesLike + trigger: BytesLike +} + +const makeReport = (upkeeps: UpkeepData[]) => { + const upkeepIds = upkeeps.map((u) => u.Id) + const performGases = upkeeps.map((u) => u.performGas) + const triggers = upkeeps.map((u) => u.trigger) + const performDatas = upkeeps.map((u) => u.performData) + return encodeReport({ + fastGasWei: gasWei, + linkUSD, + upkeepIds, + gasLimits: performGases, + triggers, + performDatas, + }) +} + +const makeLatestBlockReport = async (upkeepsIDs: BigNumberish[]) => { + const latestBlock = await ethers.provider.getBlock('latest') + const upkeeps: UpkeepData[] = [] + for (let i = 0; i < upkeepsIDs.length; i++) { + upkeeps.push({ + Id: upkeepsIDs[i], + performGas, + trigger: encodeBlockTrigger({ + blockNum: latestBlock.number, + blockHash: latestBlock.hash, + }), + performData: '0x', + }) + } + return makeReport(upkeeps) +} + +const signReport = ( + reportContext: string[], + report: any, + signers: Wallet[], +) => { + const reportDigest = ethers.utils.keccak256(report) + const packedArgs = ethers.utils.solidityPack( + ['bytes32', 'bytes32[3]'], + [reportDigest, reportContext], + ) + const packedDigest = ethers.utils.keccak256(packedArgs) + + const signatures = [] + for (const signer of signers) { + signatures.push(signer._signingKey().signDigest(packedDigest)) + } + const vs = signatures.map((i) => '0' + (i.v - 27).toString(16)).join('') + return { + vs: '0x' + vs.padEnd(64, '0'), + rs: signatures.map((i) => i.r), + ss: signatures.map((i) => i.s), + } +} + +const parseUpkeepPerformedLogs = (receipt: ContractReceipt) => { + const parsedLogs = [] + for (const rawLog of receipt.logs) { + try { + const log = registry.interface.parseLog(rawLog) + if ( + log.name == + registry.interface.events[ + 'UpkeepPerformed(uint256,bool,uint96,uint256,uint256,bytes)' + ].name + ) { + parsedLogs.push(log as unknown as UpkeepPerformedEvent) + } + } catch { + continue + } + } + return parsedLogs +} + +const parseReorgedUpkeepReportLogs = (receipt: ContractReceipt) => { + const parsedLogs = [] + for (const rawLog of receipt.logs) { + try { + const log = registry.interface.parseLog(rawLog) + if ( + log.name == + registry.interface.events['ReorgedUpkeepReport(uint256,bytes)'].name + ) { + parsedLogs.push(log as unknown as ReorgedUpkeepReportEvent) + } + } catch { + continue + } + } + return parsedLogs +} + +const parseStaleUpkeepReportLogs = (receipt: ContractReceipt) => { + const parsedLogs = [] + for (const rawLog of receipt.logs) { + try { + const log = registry.interface.parseLog(rawLog) + if ( + log.name == + registry.interface.events['StaleUpkeepReport(uint256,bytes)'].name + ) { + parsedLogs.push(log as unknown as StaleUpkeepReportEvent) + } + } catch { + continue + } + } + return parsedLogs +} + +const parseCancelledUpkeepReportLogs = (receipt: ContractReceipt) => { + const parsedLogs = [] + for (const rawLog of receipt.logs) { + try { + const log = registry.interface.parseLog(rawLog) + if ( + log.name == + registry.interface.events['CancelledUpkeepReport(uint256,bytes)'].name + ) { + parsedLogs.push(log as unknown as CancelledUpkeepReportEvent) + } + } catch { + continue + } + } + return parsedLogs +} + +describe('ZKSyncAutomationRegistry2_3', () => { + let owner: Signer + let keeper1: Signer + let keeper2: Signer + let keeper3: Signer + let keeper4: Signer + let keeper5: Signer + let nonkeeper: Signer + let signer1: Wallet + let signer2: Wallet + let signer3: Wallet + let signer4: Wallet + let signer5: Wallet + let admin: Signer + let payee1: Signer + let payee2: Signer + let payee3: Signer + let payee4: Signer + let payee5: Signer + let financeAdmin: Signer + + let upkeepId: BigNumber // conditional upkeep + let afUpkeepId: BigNumber // auto funding upkeep + let logUpkeepId: BigNumber // log trigger upkeepID + let streamsLookupUpkeepId: BigNumber // streams lookup upkeep + // const numUpkeeps = 4 // see above + let keeperAddresses: string[] + let payees: string[] + let signers: Wallet[] + let signerAddresses: string[] + let config: OnChainConfig + let baseConfig: Parameters + let upkeepManager: string + + before(async () => { + personas = (await getUsers()).personas + + const compatibleUtilsFactory = await ethers.getContractFactory( + 'AutomationCompatibleUtils', + ) + automationUtils = await compatibleUtilsFactory.deploy() + + const utilsFactory = await ethers.getContractFactory('AutomationUtils2_3') + automationUtils2_3 = await utilsFactory.deploy() + + linkTokenFactory = await ethers.getContractFactory( + 'src/v0.8/shared/test/helpers/LinkTokenTestHelper.sol:LinkTokenTestHelper', + ) + // need full path because there are two contracts with name MockV3Aggregator + mockV3AggregatorFactory = (await ethers.getContractFactory( + 'src/v0.8/tests/MockV3Aggregator.sol:MockV3Aggregator', + )) as unknown as MockV3AggregatorFactory + mockZKSyncSystemContextFactory = await ethers.getContractFactory( + 'MockZKSyncSystemContext', + ) + mockGasBoundCallerFactory = + await ethers.getContractFactory('MockGasBoundCaller') + upkeepMockFactory = await ethers.getContractFactory('UpkeepMock') + upkeepAutoFunderFactory = + await ethers.getContractFactory('UpkeepAutoFunder') + moduleBaseFactory = await ethers.getContractFactory('ChainModuleBase') + streamsLookupUpkeepFactory = await ethers.getContractFactory( + 'StreamsLookupUpkeep', + ) + + owner = personas.Default + keeper1 = personas.Carol + keeper2 = personas.Eddy + keeper3 = personas.Nancy + keeper4 = personas.Norbert + keeper5 = personas.Nick + nonkeeper = personas.Ned + admin = personas.Neil + payee1 = personas.Nelly + payee2 = personas.Norbert + payee3 = personas.Nick + payee4 = personas.Eddy + payee5 = personas.Carol + upkeepManager = await personas.Norbert.getAddress() + financeAdmin = personas.Nick + // signers + signer1 = new ethers.Wallet( + '0x7777777000000000000000000000000000000000000000000000000000000001', + ) + signer2 = new ethers.Wallet( + '0x7777777000000000000000000000000000000000000000000000000000000002', + ) + signer3 = new ethers.Wallet( + '0x7777777000000000000000000000000000000000000000000000000000000003', + ) + signer4 = new ethers.Wallet( + '0x7777777000000000000000000000000000000000000000000000000000000004', + ) + signer5 = new ethers.Wallet( + '0x7777777000000000000000000000000000000000000000000000000000000005', + ) + + keeperAddresses = [ + await keeper1.getAddress(), + await keeper2.getAddress(), + await keeper3.getAddress(), + await keeper4.getAddress(), + await keeper5.getAddress(), + ] + payees = [ + await payee1.getAddress(), + await payee2.getAddress(), + await payee3.getAddress(), + await payee4.getAddress(), + await payee5.getAddress(), + ] + signers = [signer1, signer2, signer3, signer4, signer5] + + // We append 26 random addresses to keepers, payees and signers to get a system of 31 oracles + // This allows f value of 1 - 10 + for (let i = 0; i < 26; i++) { + keeperAddresses.push(randomAddress()) + payees.push(randomAddress()) + signers.push(ethers.Wallet.createRandom()) + } + signerAddresses = [] + for (const signer of signers) { + signerAddresses.push(await signer.getAddress()) + } + + logTriggerConfig = + '0x' + + automationUtils.interface + .encodeFunctionData('_logTriggerConfig', [ + { + contractAddress: randomAddress(), + filterSelector: 0, + topic0: ethers.utils.randomBytes(32), + topic1: ethers.utils.randomBytes(32), + topic2: ethers.utils.randomBytes(32), + topic3: ethers.utils.randomBytes(32), + }, + ]) + .slice(10) + }) + + // This function is similar to registry's _calculatePaymentAmount + // It uses global fastGasWei, linkEth, and assumes isExecution = false (gasFee = fastGasWei*multiplier) + // rest of the parameters are the same + const linkForGas = ( + upkeepGasSpent: BigNumber, + gasOverhead: BigNumber, + gasMultiplier: BigNumber, + premiumPPB: BigNumber, + flatFee: BigNumber, // in millicents + ) => { + const gasSpent = gasOverhead.add(BigNumber.from(upkeepGasSpent)) + const gasPayment = gasWei + .mul(gasMultiplier) + .mul(gasSpent) + .mul(nativeUSD) + .div(linkUSD) + + const premium = gasWei + .mul(gasMultiplier) + .mul(upkeepGasSpent) + .mul(premiumPPB) + .mul(nativeUSD) + .div(paymentPremiumBase) + .add(flatFee.mul(BigNumber.from(10).pow(21))) + .div(linkUSD) + + return { + total: gasPayment.add(premium), + gasPayment, + premium, + } + } + + const verifyMaxPayment = async ( + registry: IAutomationRegistry, + chainModule: IChainModule, + ) => { + type TestCase = { + name: string + multiplier: number + gas: number + premium: number + flatFee: number + } + + const tests: TestCase[] = [ + { + name: 'no fees', + multiplier: 1, + gas: 100000, + premium: 0, + flatFee: 0, + }, + { + name: 'basic fees', + multiplier: 1, + gas: 100000, + premium: 250000000, + flatFee: 1000000, + }, + { + name: 'max fees', + multiplier: 3, + gas: 10000000, + premium: 250000000, + flatFee: 1000000, + }, + ] + + const fPlusOne = BigNumber.from(f + 1) + const chainModuleOverheads = await chainModule.getGasOverhead() + const totalConditionalOverhead = registryConditionalOverhead + .add(registryPerSignerGasOverhead.mul(fPlusOne)) + .add(chainModuleOverheads.chainModuleFixedOverhead) + + const totalLogOverhead = registryLogOverhead + .add(registryPerSignerGasOverhead.mul(fPlusOne)) + .add(chainModuleOverheads.chainModuleFixedOverhead) + + const financeAdminAddress = await financeAdmin.getAddress() + + for (const test of tests) { + await registry.connect(owner).setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + { + checkGasLimit, + stalenessSeconds, + gasCeilingMultiplier: test.multiplier, + maxCheckDataSize, + maxPerformDataSize, + maxRevertDataSize, + maxPerformGas, + fallbackGasPrice, + fallbackLinkPrice, + fallbackNativePrice, + transcoder: transcoder.address, + registrars: [], + upkeepPrivilegeManager: upkeepManager, + chainModule: chainModule.address, + reorgProtectionEnabled: true, + financeAdmin: financeAdminAddress, + }, + offchainVersion, + offchainBytes, + [linkToken.address], + [ + { + gasFeePPB: test.premium, + flatFeeMilliCents: test.flatFee, + priceFeed: linkUSDFeed.address, + fallbackPrice: fallbackLinkPrice, + minSpend: minUpkeepSpend, + decimals: 18, + }, + ], + ) + + const conditionalPrice = await registry.getMaxPaymentForGas( + upkeepId, + Trigger.CONDITION, + test.gas, + linkToken.address, + ) + expect(conditionalPrice).to.equal( + linkForGas( + BigNumber.from(test.gas), + totalConditionalOverhead, + BigNumber.from(test.multiplier), + BigNumber.from(test.premium), + BigNumber.from(test.flatFee), + ).total, + ) + + const logPrice = await registry.getMaxPaymentForGas( + upkeepId, + Trigger.LOG, + test.gas, + linkToken.address, + ) + expect(logPrice).to.equal( + linkForGas( + BigNumber.from(test.gas), + totalLogOverhead, + BigNumber.from(test.multiplier), + BigNumber.from(test.premium), + BigNumber.from(test.flatFee), + ).total, + ) + } + } + + const verifyConsistentAccounting = async ( + maxAllowedSpareChange: BigNumber, + ) => { + const expectedLinkBalance = await registry.getReserveAmount( + linkToken.address, + ) + const linkTokenBalance = await linkToken.balanceOf(registry.address) + const upkeepIdBalance = (await registry.getUpkeep(upkeepId)).balance + let totalKeeperBalance = BigNumber.from(0) + for (let i = 0; i < keeperAddresses.length; i++) { + totalKeeperBalance = totalKeeperBalance.add( + (await registry.getTransmitterInfo(keeperAddresses[i])).balance, + ) + } + + const linkAvailableForPayment = await registry.linkAvailableForPayment() + assert.isTrue(expectedLinkBalance.eq(linkTokenBalance)) + assert.isTrue( + upkeepIdBalance + .add(totalKeeperBalance) + .add(linkAvailableForPayment) + .lte(expectedLinkBalance), + ) + assert.isTrue( + expectedLinkBalance + .sub(upkeepIdBalance) + .sub(totalKeeperBalance) + .sub(linkAvailableForPayment) + .lte(maxAllowedSpareChange), + ) + } + + interface GetTransmitTXOptions { + numSigners?: number + startingSignerIndex?: number + gasLimit?: BigNumberish + gasPrice?: BigNumberish + performGas?: BigNumberish + performDatas?: string[] + checkBlockNum?: number + checkBlockHash?: string + logBlockHash?: BytesLike + txHash?: BytesLike + logIndex?: number + timestamp?: number + } + + const getTransmitTx = async ( + registry: IAutomationRegistry, + transmitter: Signer, + upkeepIds: BigNumber[], + overrides: GetTransmitTXOptions = {}, + ) => { + const latestBlock = await ethers.provider.getBlock('latest') + const configDigest = (await registry.getState()).state.latestConfigDigest + const config = { + numSigners: f + 1, + startingSignerIndex: 0, + performDatas: undefined, + performGas, + checkBlockNum: latestBlock.number, + checkBlockHash: latestBlock.hash, + logIndex: 0, + txHash: undefined, // assigned uniquely below + logBlockHash: undefined, // assigned uniquely below + timestamp: now(), + gasLimit: undefined, + gasPrice: undefined, + } + Object.assign(config, overrides) + const upkeeps: UpkeepData[] = [] + for (let i = 0; i < upkeepIds.length; i++) { + let trigger: string + switch (getTriggerType(upkeepIds[i])) { + case Trigger.CONDITION: + trigger = encodeBlockTrigger({ + blockNum: config.checkBlockNum, + blockHash: config.checkBlockHash, + }) + break + case Trigger.LOG: + trigger = encodeLogTrigger({ + logBlockHash: config.logBlockHash || ethers.utils.randomBytes(32), + txHash: config.txHash || ethers.utils.randomBytes(32), + logIndex: config.logIndex, + blockNum: config.checkBlockNum, + blockHash: config.checkBlockHash, + }) + break + } + upkeeps.push({ + Id: upkeepIds[i], + performGas: config.performGas, + trigger, + performData: config.performDatas ? config.performDatas[i] : '0x', + }) + } + + const report = makeReport(upkeeps) + const reportContext = [configDigest, epochAndRound5_1, emptyBytes32] + const sigs = signReport( + reportContext, + report, + signers.slice( + config.startingSignerIndex, + config.startingSignerIndex + config.numSigners, + ), + ) + + type txOverride = { + gasLimit?: BigNumberish | Promise + gasPrice?: BigNumberish | Promise + } + const txOverrides: txOverride = {} + if (config.gasLimit) { + txOverrides.gasLimit = config.gasLimit + } + if (config.gasPrice) { + txOverrides.gasPrice = config.gasPrice + } + + return registry + .connect(transmitter) + .transmit( + [configDigest, epochAndRound5_1, emptyBytes32], + report, + sigs.rs, + sigs.ss, + sigs.vs, + txOverrides, + ) + } + + const getTransmitTxWithReport = async ( + registry: IAutomationRegistry, + transmitter: Signer, + report: BytesLike, + ) => { + const configDigest = (await registry.getState()).state.latestConfigDigest + const reportContext = [configDigest, epochAndRound5_1, emptyBytes32] + const sigs = signReport(reportContext, report, signers.slice(0, f + 1)) + + return registry + .connect(transmitter) + .transmit( + [configDigest, epochAndRound5_1, emptyBytes32], + report, + sigs.rs, + sigs.ss, + sigs.vs, + ) + } + + const setup = async () => { + linkToken = await linkTokenFactory.connect(owner).deploy() + gasPriceFeed = await mockV3AggregatorFactory + .connect(owner) + .deploy(0, gasWei) + linkUSDFeed = await mockV3AggregatorFactory + .connect(owner) + .deploy(8, linkUSD) + nativeUSDFeed = await mockV3AggregatorFactory + .connect(owner) + .deploy(8, nativeUSD) + const upkeepTranscoderFactory = await ethers.getContractFactory( + 'UpkeepTranscoder5_0', + ) + transcoder = await upkeepTranscoderFactory.connect(owner).deploy() + mockZKSyncSystemContext = await mockZKSyncSystemContextFactory + .connect(owner) + .deploy() + mockGasBoundCaller = await mockGasBoundCallerFactory.connect(owner).deploy() + moduleBase = await moduleBaseFactory.connect(owner).deploy() + streamsLookupUpkeep = await streamsLookupUpkeepFactory + .connect(owner) + .deploy( + BigNumber.from('10000'), + BigNumber.from('100'), + false /* useArbBlock */, + true /* staging */, + false /* verify mercury response */, + ) + + const zksyncSystemContextCode = await ethers.provider.send('eth_getCode', [ + mockZKSyncSystemContext.address, + ]) + await ethers.provider.send('hardhat_setCode', [ + '0x000000000000000000000000000000000000800B', + zksyncSystemContextCode, + ]) + + const gasBoundCallerCode = await ethers.provider.send('eth_getCode', [ + mockGasBoundCaller.address, + ]) + await ethers.provider.send('hardhat_setCode', [ + '0xc706EC7dfA5D4Dc87f29f859094165E8290530f5', + gasBoundCallerCode, + ]) + + const financeAdminAddress = await financeAdmin.getAddress() + + config = { + checkGasLimit, + stalenessSeconds, + gasCeilingMultiplier, + maxCheckDataSize, + maxPerformDataSize, + maxRevertDataSize, + maxPerformGas, + fallbackGasPrice, + fallbackLinkPrice, + fallbackNativePrice, + transcoder: transcoder.address, + registrars: [], + upkeepPrivilegeManager: upkeepManager, + chainModule: moduleBase.address, + reorgProtectionEnabled: true, + financeAdmin: financeAdminAddress, + } + + baseConfig = [ + signerAddresses, + keeperAddresses, + f, + config, + offchainVersion, + offchainBytes, + [linkToken.address], + [ + { + gasFeePPB: paymentPremiumPPB, + flatFeeMilliCents, + priceFeed: linkUSDFeed.address, + fallbackPrice: fallbackLinkPrice, + minSpend: minUpkeepSpend, + decimals: 18, + }, + ], + ] + + const registryParams: Parameters = [ + owner, + linkToken.address, + linkUSDFeed.address, + nativeUSDFeed.address, + gasPriceFeed.address, + zeroAddress, + 0, // onchain payout mode + wrappedNativeTokenAddress, + ] + + registry = await deployZKSyncRegistry23(...registryParams) + mgRegistry = await deployZKSyncRegistry23(...registryParams) + + registryConditionalOverhead = await registry.getConditionalGasOverhead() + registryLogOverhead = await registry.getLogGasOverhead() + registryPerSignerGasOverhead = await registry.getPerSignerGasOverhead() + // registryPerPerformByteGasOverhead = + // await registry.getPerPerformByteGasOverhead() + // registryTransmitCalldataFixedBytesOverhead = + // await registry.getTransmitCalldataFixedBytesOverhead() + // registryTransmitCalldataPerSignerBytesOverhead = + // await registry.getTransmitCalldataPerSignerBytesOverhead() + cancellationDelay = (await registry.getCancellationDelay()).toNumber() + + await registry.connect(owner).setConfigTypeSafe(...baseConfig) + await mgRegistry.connect(owner).setConfigTypeSafe(...baseConfig) + for (const reg of [registry, mgRegistry]) { + await reg.connect(owner).setPayees(payees) + await linkToken.connect(admin).approve(reg.address, toWei('1000')) + await linkToken.connect(owner).approve(reg.address, toWei('1000')) + } + + mock = await upkeepMockFactory.deploy() + await linkToken + .connect(owner) + .transfer(await admin.getAddress(), toWei('1000')) + let tx = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + randomBytes, + '0x', + '0x', + ) + upkeepId = await getUpkeepID(tx) + + autoFunderUpkeep = await upkeepAutoFunderFactory + .connect(owner) + .deploy(linkToken.address, registry.address) + tx = await registry + .connect(owner) + .registerUpkeep( + autoFunderUpkeep.address, + performGas, + autoFunderUpkeep.address, + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + afUpkeepId = await getUpkeepID(tx) + + ltUpkeep = await deployMockContract(owner, ILogAutomationactory.abi) + tx = await registry + .connect(owner) + .registerUpkeep( + ltUpkeep.address, + performGas, + await admin.getAddress(), + Trigger.LOG, + linkToken.address, + '0x', + logTriggerConfig, + emptyBytes, + ) + logUpkeepId = await getUpkeepID(tx) + + await autoFunderUpkeep.setUpkeepId(afUpkeepId) + // Give enough funds for upkeep as well as to the upkeep contract + await linkToken + .connect(owner) + .transfer(autoFunderUpkeep.address, toWei('1000')) + + tx = await registry + .connect(owner) + .registerUpkeep( + streamsLookupUpkeep.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + streamsLookupUpkeepId = await getUpkeepID(tx) + } + + const getMultipleUpkeepsDeployedAndFunded = async ( + numPassingConditionalUpkeeps: number, + numPassingLogUpkeeps: number, + numFailingUpkeeps: number, + ) => { + const passingConditionalUpkeepIds = [] + const passingLogUpkeepIds = [] + const failingUpkeepIds = [] + for (let i = 0; i < numPassingConditionalUpkeeps; i++) { + const mock = await upkeepMockFactory.deploy() + await mock.setCanPerform(true) + await mock.setPerformGasToBurn(BigNumber.from('0')) + const tx = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + const condUpkeepId = await getUpkeepID(tx) + passingConditionalUpkeepIds.push(condUpkeepId) + + // Add funds to passing upkeeps + await registry.connect(admin).addFunds(condUpkeepId, toWei('100')) + } + for (let i = 0; i < numPassingLogUpkeeps; i++) { + const mock = await upkeepMockFactory.deploy() + await mock.setCanPerform(true) + await mock.setPerformGasToBurn(BigNumber.from('0')) + const tx = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.LOG, + linkToken.address, + '0x', + logTriggerConfig, + emptyBytes, + ) + const logUpkeepId = await getUpkeepID(tx) + passingLogUpkeepIds.push(logUpkeepId) + + // Add funds to passing upkeeps + await registry.connect(admin).addFunds(logUpkeepId, toWei('100')) + } + for (let i = 0; i < numFailingUpkeeps; i++) { + const mock = await upkeepMockFactory.deploy() + await mock.setCanPerform(true) + await mock.setPerformGasToBurn(BigNumber.from('0')) + const tx = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + const failingUpkeepId = await getUpkeepID(tx) + failingUpkeepIds.push(failingUpkeepId) + } + return { + passingConditionalUpkeepIds, + passingLogUpkeepIds, + failingUpkeepIds, + } + } + + beforeEach(async () => { + await loadFixture(setup) + }) + + describe('#transmit', () => { + const fArray = [1, 5, 10] + + it('reverts when registry is paused', async () => { + await registry.connect(owner).pause() + await evmRevertCustomError( + getTransmitTx(registry, keeper1, [upkeepId]), + registry, + 'RegistryPaused', + ) + }) + + it('reverts when called by non active transmitter', async () => { + await evmRevertCustomError( + getTransmitTx(registry, payee1, [upkeepId]), + registry, + 'OnlyActiveTransmitters', + ) + }) + + it('reverts when report data lengths mismatches', async () => { + const upkeepIds = [] + const gasLimits: BigNumber[] = [] + const triggers: string[] = [] + const performDatas = [] + + upkeepIds.push(upkeepId) + gasLimits.push(performGas) + triggers.push('0x') + performDatas.push('0x') + // Push an extra perform data + performDatas.push('0x') + + const report = encodeReport({ + fastGasWei: 0, + linkUSD: 0, + upkeepIds, + gasLimits, + triggers, + performDatas, + }) + + await evmRevertCustomError( + getTransmitTxWithReport(registry, keeper1, report), + registry, + 'InvalidReport', + ) + }) + + it('returns early when invalid upkeepIds are included in report', async () => { + const tx = await getTransmitTx(registry, keeper1, [ + upkeepId.add(BigNumber.from('1')), + ]) + + const receipt = await tx.wait() + const cancelledUpkeepReportLogs = parseCancelledUpkeepReportLogs(receipt) + // exactly 1 CancelledUpkeepReport log should be emitted + assert.equal(cancelledUpkeepReportLogs.length, 1) + }) + + it('performs even when the upkeep has insufficient funds and the upkeep pays out all the remaining balance', async () => { + // add very little fund to this upkeep + await registry.connect(admin).addFunds(upkeepId, BigNumber.from(10)) + const tx = await getTransmitTx(registry, keeper1, [upkeepId]) + const receipt = await tx.wait() + // the upkeep is underfunded in transmit but still performed + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + assert.equal(upkeepPerformedLogs.length, 1) + const balance = (await registry.getUpkeep(upkeepId)).balance + assert.equal(balance.toNumber(), 0) + }) + + context('When the upkeep is funded', async () => { + beforeEach(async () => { + // Fund the upkeep + await Promise.all([ + registry.connect(admin).addFunds(upkeepId, toWei('100')), + registry.connect(admin).addFunds(logUpkeepId, toWei('100')), + ]) + }) + + it('handles duplicate upkeepIDs', async () => { + const tests: [string, BigNumber, number, number][] = [ + // [name, upkeep, num stale, num performed] + ['conditional', upkeepId, 1, 1], // checkBlocks must be sequential + ['log-trigger', logUpkeepId, 0, 2], // logs are deduped based on the "trigger ID" + ] + for (const [type, id, nStale, nPerformed] of tests) { + const tx = await getTransmitTx(registry, keeper1, [id, id]) + const receipt = await tx.wait() + const staleUpkeepReport = parseStaleUpkeepReportLogs(receipt) + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + assert.equal( + staleUpkeepReport.length, + nStale, + `wrong log count for ${type} upkeep`, + ) + assert.equal( + upkeepPerformedLogs.length, + nPerformed, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('handles duplicate log triggers', async () => { + const logBlockHash = ethers.utils.randomBytes(32) + const txHash = ethers.utils.randomBytes(32) + const logIndex = 0 + const expectedDedupKey = ethers.utils.solidityKeccak256( + ['uint256', 'bytes32', 'bytes32', 'uint32'], + [logUpkeepId, logBlockHash, txHash, logIndex], + ) + assert.isFalse(await registry.hasDedupKey(expectedDedupKey)) + const tx = await getTransmitTx( + registry, + keeper1, + [logUpkeepId, logUpkeepId], + { logBlockHash, txHash, logIndex }, // will result in the same dedup key + ) + const receipt = await tx.wait() + const staleUpkeepReport = parseStaleUpkeepReportLogs(receipt) + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + assert.equal(staleUpkeepReport.length, 1) + assert.equal(upkeepPerformedLogs.length, 1) + assert.isTrue(await registry.hasDedupKey(expectedDedupKey)) + await expect(tx) + .to.emit(registry, 'DedupKeyAdded') + .withArgs(expectedDedupKey) + }) + + it('returns early when check block number is less than last perform (block)', async () => { + // First perform an upkeep to put last perform block number on upkeep state + const tx = await getTransmitTx(registry, keeper1, [upkeepId]) + await tx.wait() + const lastPerformed = (await registry.getUpkeep(upkeepId)) + .lastPerformedBlockNumber + const lastPerformBlock = await ethers.provider.getBlock(lastPerformed) + assert.equal(lastPerformed.toString(), tx.blockNumber?.toString()) + // Try to transmit a report which has checkBlockNumber = lastPerformed-1, should result in stale report + const transmitTx = await getTransmitTx(registry, keeper1, [upkeepId], { + checkBlockNum: lastPerformBlock.number - 1, + checkBlockHash: lastPerformBlock.parentHash, + }) + const receipt = await transmitTx.wait() + const staleUpkeepReportLogs = parseStaleUpkeepReportLogs(receipt) + // exactly 1 StaleUpkeepReportLogs log should be emitted + assert.equal(staleUpkeepReportLogs.length, 1) + }) + + it('handles case when check block hash does not match', async () => { + const tests: [string, BigNumber][] = [ + ['conditional', upkeepId], + ['log-trigger', logUpkeepId], + ] + for (const [type, id] of tests) { + const latestBlock = await ethers.provider.getBlock('latest') + // Try to transmit a report which has incorrect checkBlockHash + const tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: latestBlock.number - 1, + checkBlockHash: latestBlock.hash, // should be latestBlock.parentHash + }) + + const receipt = await tx.wait() + const reorgedUpkeepReportLogs = parseReorgedUpkeepReportLogs(receipt) + // exactly 1 ReorgedUpkeepReportLogs log should be emitted + assert.equal( + reorgedUpkeepReportLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('handles case when check block number is older than 256 blocks', async () => { + for (let i = 0; i < 256; i++) { + await ethers.provider.send('evm_mine', []) + } + const tests: [string, BigNumber][] = [ + ['conditional', upkeepId], + ['log-trigger', logUpkeepId], + ] + for (const [type, id] of tests) { + const latestBlock = await ethers.provider.getBlock('latest') + const old = await ethers.provider.getBlock(latestBlock.number - 256) + // Try to transmit a report which has incorrect checkBlockHash + const tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: old.number, + checkBlockHash: old.hash, + }) + + const receipt = await tx.wait() + const reorgedUpkeepReportLogs = parseReorgedUpkeepReportLogs(receipt) + // exactly 1 ReorgedUpkeepReportLogs log should be emitted + assert.equal( + reorgedUpkeepReportLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('allows bypassing reorg protection with empty blockhash', async () => { + const tests: [string, BigNumber][] = [ + ['conditional', upkeepId], + ['log-trigger', logUpkeepId], + ] + for (const [type, id] of tests) { + const latestBlock = await ethers.provider.getBlock('latest') + const tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: latestBlock.number, + checkBlockHash: emptyBytes32, + }) + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + assert.equal( + upkeepPerformedLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('allows bypassing reorg protection with reorgProtectionEnabled false config', async () => { + const tests: [string, BigNumber][] = [ + ['conditional', upkeepId], + ['log-trigger', logUpkeepId], + ] + const newConfig = config + newConfig.reorgProtectionEnabled = false + await registry // used to test initial configurations + .connect(owner) + .setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + newConfig, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ) + + for (const [type, id] of tests) { + const latestBlock = await ethers.provider.getBlock('latest') + // Try to transmit a report which has incorrect checkBlockHash + const tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: latestBlock.number - 1, + checkBlockHash: latestBlock.hash, // should be latestBlock.parentHash + }) + + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + assert.equal( + upkeepPerformedLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('allows very old trigger block numbers when bypassing reorg protection with reorgProtectionEnabled config', async () => { + const newConfig = config + newConfig.reorgProtectionEnabled = false + await registry // used to test initial configurations + .connect(owner) + .setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + newConfig, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ) + for (let i = 0; i < 256; i++) { + await ethers.provider.send('evm_mine', []) + } + const tests: [string, BigNumber][] = [ + ['conditional', upkeepId], + ['log-trigger', logUpkeepId], + ] + for (const [type, id] of tests) { + const latestBlock = await ethers.provider.getBlock('latest') + const old = await ethers.provider.getBlock(latestBlock.number - 256) + // Try to transmit a report which has incorrect checkBlockHash + const tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: old.number, + checkBlockHash: old.hash, + }) + + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + assert.equal( + upkeepPerformedLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('allows very old trigger block numbers when bypassing reorg protection with empty blockhash', async () => { + // mine enough blocks so that blockhash(1) is unavailable + for (let i = 0; i <= 256; i++) { + await ethers.provider.send('evm_mine', []) + } + const tests: [string, BigNumber][] = [ + ['conditional', upkeepId], + ['log-trigger', logUpkeepId], + ] + for (const [type, id] of tests) { + const tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: 1, + checkBlockHash: emptyBytes32, + }) + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + assert.equal( + upkeepPerformedLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('returns early when future block number is provided as trigger, irrespective of blockhash being present', async () => { + const tests: [string, BigNumber][] = [ + ['conditional', upkeepId], + ['log-trigger', logUpkeepId], + ] + for (const [type, id] of tests) { + const latestBlock = await ethers.provider.getBlock('latest') + + // Should fail when blockhash is empty + let tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: latestBlock.number + 100, + checkBlockHash: emptyBytes32, + }) + let receipt = await tx.wait() + let reorgedUpkeepReportLogs = parseReorgedUpkeepReportLogs(receipt) + // exactly 1 ReorgedUpkeepReportLogs log should be emitted + assert.equal( + reorgedUpkeepReportLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + + // Should also fail when blockhash is not empty + tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: latestBlock.number + 100, + checkBlockHash: latestBlock.hash, + }) + receipt = await tx.wait() + reorgedUpkeepReportLogs = parseReorgedUpkeepReportLogs(receipt) + // exactly 1 ReorgedUpkeepReportLogs log should be emitted + assert.equal( + reorgedUpkeepReportLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('returns early when future block number is provided as trigger, irrespective of reorgProtectionEnabled config', async () => { + const newConfig = config + newConfig.reorgProtectionEnabled = false + await registry // used to test initial configurations + .connect(owner) + .setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + newConfig, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ) + const tests: [string, BigNumber][] = [ + ['conditional', upkeepId], + ['log-trigger', logUpkeepId], + ] + for (const [type, id] of tests) { + const latestBlock = await ethers.provider.getBlock('latest') + + // Should fail when blockhash is empty + let tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: latestBlock.number + 100, + checkBlockHash: emptyBytes32, + }) + let receipt = await tx.wait() + let reorgedUpkeepReportLogs = parseReorgedUpkeepReportLogs(receipt) + // exactly 1 ReorgedUpkeepReportLogs log should be emitted + assert.equal( + reorgedUpkeepReportLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + + // Should also fail when blockhash is not empty + tx = await getTransmitTx(registry, keeper1, [id], { + checkBlockNum: latestBlock.number + 100, + checkBlockHash: latestBlock.hash, + }) + receipt = await tx.wait() + reorgedUpkeepReportLogs = parseReorgedUpkeepReportLogs(receipt) + // exactly 1 ReorgedUpkeepReportLogs log should be emitted + assert.equal( + reorgedUpkeepReportLogs.length, + 1, + `wrong log count for ${type} upkeep`, + ) + } + }) + + it('returns early when upkeep is cancelled and cancellation delay has gone', async () => { + const latestBlockReport = await makeLatestBlockReport([upkeepId]) + await registry.connect(admin).cancelUpkeep(upkeepId) + + for (let i = 0; i < cancellationDelay; i++) { + await ethers.provider.send('evm_mine', []) + } + + const tx = await getTransmitTxWithReport( + registry, + keeper1, + latestBlockReport, + ) + + const receipt = await tx.wait() + const cancelledUpkeepReportLogs = + parseCancelledUpkeepReportLogs(receipt) + // exactly 1 CancelledUpkeepReport log should be emitted + assert.equal(cancelledUpkeepReportLogs.length, 1) + }) + + it('does not revert if the target cannot execute', async () => { + await mock.setCanPerform(false) + const tx = await getTransmitTx(registry, keeper1, [upkeepId]) + + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + // exactly 1 Upkeep Performed should be emitted + assert.equal(upkeepPerformedLogs.length, 1) + const upkeepPerformedLog = upkeepPerformedLogs[0] + + const success = upkeepPerformedLog.args.success + assert.equal(success, false) + }) + + it('does not revert if the target runs out of gas', async () => { + await mock.setCanPerform(false) + + const tx = await getTransmitTx(registry, keeper1, [upkeepId], { + performGas: 10, // too little gas + }) + + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + // exactly 1 Upkeep Performed should be emitted + assert.equal(upkeepPerformedLogs.length, 1) + const upkeepPerformedLog = upkeepPerformedLogs[0] + + const success = upkeepPerformedLog.args.success + assert.equal(success, false) + }) + + it('reverts if not enough gas supplied', async () => { + await mock.setCanPerform(true) + await evmRevert( + getTransmitTx(registry, keeper1, [upkeepId], { + gasLimit: BigNumber.from(150000), + }), + ) + }) + + it('executes the data passed to the registry', async () => { + await mock.setCanPerform(true) + + const tx = await getTransmitTx(registry, keeper1, [upkeepId], { + performDatas: [randomBytes], + }) + const receipt = await tx.wait() + + const upkeepPerformedWithABI = [ + 'event UpkeepPerformedWith(bytes upkeepData)', + ] + const iface = new ethers.utils.Interface(upkeepPerformedWithABI) + const parsedLogs = [] + for (let i = 0; i < receipt.logs.length; i++) { + const log = receipt.logs[i] + try { + parsedLogs.push(iface.parseLog(log)) + } catch (e) { + // ignore log + } + } + assert.equal(parsedLogs.length, 1) + assert.equal(parsedLogs[0].args.upkeepData, randomBytes) + }) + + it('uses actual execution price for payment and premium calculation', async () => { + // Actual multiplier is 2, but we set gasPrice to be == gasWei + const gasPrice = gasWei + await mock.setCanPerform(true) + const registryPremiumBefore = (await registry.getState()).state + .totalPremium + const tx = await getTransmitTx(registry, keeper1, [upkeepId], { + gasPrice, + }) + const receipt = await tx.wait() + const registryPremiumAfter = (await registry.getState()).state + .totalPremium + const premium = registryPremiumAfter.sub(registryPremiumBefore) + + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + // exactly 1 Upkeep Performed should be emitted + assert.equal(upkeepPerformedLogs.length, 1) + const upkeepPerformedLog = upkeepPerformedLogs[0] + + const gasUsed = upkeepPerformedLog.args.gasUsed // 14657 gasUsed + const gasOverhead = upkeepPerformedLog.args.gasOverhead // 137230 gasOverhead + const totalPayment = upkeepPerformedLog.args.totalPayment + + assert.equal( + linkForGas( + gasUsed, + gasOverhead, + BigNumber.from('1'), // Not the config multiplier, but the actual gas used + paymentPremiumPPB, + flatFeeMilliCents, + // pubdataGas.mul(gasPrice), + ).total.toString(), + totalPayment.toString(), + ) + + assert.equal( + linkForGas( + gasUsed, + gasOverhead, + BigNumber.from('1'), // Not the config multiplier, but the actual gas used + paymentPremiumPPB, + flatFeeMilliCents, + // pubdataGas.mul(gasPrice), + ).premium.toString(), + premium.toString(), + ) + }) + + it('only pays at a rate up to the gas ceiling [ @skip-coverage ]', async () => { + // Actual multiplier is 2, but we set gasPrice to be 10x + const gasPrice = gasWei.mul(BigNumber.from('10')) + await mock.setCanPerform(true) + + const tx = await getTransmitTx(registry, keeper1, [upkeepId], { + gasPrice, + }) + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + // exactly 1 Upkeep Performed should be emitted + assert.equal(upkeepPerformedLogs.length, 1) + const upkeepPerformedLog = upkeepPerformedLogs[0] + + const gasUsed = upkeepPerformedLog.args.gasUsed + const gasOverhead = upkeepPerformedLog.args.gasOverhead + const totalPayment = upkeepPerformedLog.args.totalPayment + + assert.equal( + linkForGas( + gasUsed, + gasOverhead, + gasCeilingMultiplier, // Should be same with exisitng multiplier + paymentPremiumPPB, + flatFeeMilliCents, + // pubdataGas.mul(gasPrice), + ).total.toString(), + totalPayment.toString(), + ) + }) + + itMaybe('can self fund', async () => { + const maxPayment = await registry.getMaxPaymentForGas( + upkeepId, + Trigger.CONDITION, + performGas, + linkToken.address, + ) + + // First set auto funding amount to 0 and verify that balance is deducted upon performUpkeep + let initialBalance = toWei('100') + await registry.connect(owner).addFunds(afUpkeepId, initialBalance) + await autoFunderUpkeep.setAutoFundLink(0) + await autoFunderUpkeep.setIsEligible(true) + await getTransmitTx(registry, keeper1, [afUpkeepId]) + + let postUpkeepBalance = (await registry.getUpkeep(afUpkeepId)).balance + assert.isTrue(postUpkeepBalance.lt(initialBalance)) // Balance should be deducted + assert.isTrue(postUpkeepBalance.gte(initialBalance.sub(maxPayment))) // Balance should not be deducted more than maxPayment + + // Now set auto funding amount to 100 wei and verify that the balance increases + initialBalance = postUpkeepBalance + const autoTopupAmount = toWei('100') + await autoFunderUpkeep.setAutoFundLink(autoTopupAmount) + await autoFunderUpkeep.setIsEligible(true) + await getTransmitTx(registry, keeper1, [afUpkeepId]) + + postUpkeepBalance = (await registry.getUpkeep(afUpkeepId)).balance + // Balance should increase by autoTopupAmount and decrease by max maxPayment + assert.isTrue( + postUpkeepBalance.gte( + initialBalance.add(autoTopupAmount).sub(maxPayment), + ), + ) + }) + + it('can self cancel', async () => { + await registry.connect(owner).addFunds(afUpkeepId, toWei('100')) + + await autoFunderUpkeep.setIsEligible(true) + await autoFunderUpkeep.setShouldCancel(true) + + let registration = await registry.getUpkeep(afUpkeepId) + const oldExpiration = registration.maxValidBlocknumber + + // Do the thing + await getTransmitTx(registry, keeper1, [afUpkeepId]) + + // Verify upkeep gets cancelled + registration = await registry.getUpkeep(afUpkeepId) + const newExpiration = registration.maxValidBlocknumber + assert.isTrue(newExpiration.lt(oldExpiration)) + }) + + it('reverts when configDigest mismatches', async () => { + const report = await makeLatestBlockReport([upkeepId]) + const reportContext = [emptyBytes32, epochAndRound5_1, emptyBytes32] // wrong config digest + const sigs = signReport(reportContext, report, signers.slice(0, f + 1)) + await evmRevertCustomError( + registry + .connect(keeper1) + .transmit( + [reportContext[0], reportContext[1], reportContext[2]], + report, + sigs.rs, + sigs.ss, + sigs.vs, + ), + registry, + 'ConfigDigestMismatch', + ) + }) + + it('reverts with incorrect number of signatures', async () => { + const configDigest = (await registry.getState()).state + .latestConfigDigest + const report = await makeLatestBlockReport([upkeepId]) + const reportContext = [configDigest, epochAndRound5_1, emptyBytes32] // wrong config digest + const sigs = signReport(reportContext, report, signers.slice(0, f + 2)) + await evmRevertCustomError( + registry + .connect(keeper1) + .transmit( + [reportContext[0], reportContext[1], reportContext[2]], + report, + sigs.rs, + sigs.ss, + sigs.vs, + ), + registry, + 'IncorrectNumberOfSignatures', + ) + }) + + it('reverts with invalid signature for inactive signers', async () => { + const configDigest = (await registry.getState()).state + .latestConfigDigest + const report = await makeLatestBlockReport([upkeepId]) + const reportContext = [configDigest, epochAndRound5_1, emptyBytes32] // wrong config digest + const sigs = signReport(reportContext, report, [ + new ethers.Wallet(ethers.Wallet.createRandom()), + new ethers.Wallet(ethers.Wallet.createRandom()), + ]) + await evmRevertCustomError( + registry + .connect(keeper1) + .transmit( + [reportContext[0], reportContext[1], reportContext[2]], + report, + sigs.rs, + sigs.ss, + sigs.vs, + ), + registry, + 'OnlyActiveSigners', + ) + }) + + it('reverts with invalid signature for duplicated signers', async () => { + const configDigest = (await registry.getState()).state + .latestConfigDigest + const report = await makeLatestBlockReport([upkeepId]) + const reportContext = [configDigest, epochAndRound5_1, emptyBytes32] // wrong config digest + const sigs = signReport(reportContext, report, [signer1, signer1]) + await evmRevertCustomError( + registry + .connect(keeper1) + .transmit( + [reportContext[0], reportContext[1], reportContext[2]], + report, + sigs.rs, + sigs.ss, + sigs.vs, + ), + registry, + 'DuplicateSigners', + ) + }) + + itMaybe( + 'has a large enough gas overhead to cover upkeep that use all its gas [ @skip-coverage ]', + async () => { + await registry.connect(owner).setConfigTypeSafe( + signerAddresses, + keeperAddresses, + 10, // maximise f to maximise overhead + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ) + const tx = await registry.connect(owner).registerUpkeep( + mock.address, + maxPerformGas, // max allowed gas + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + const testUpkeepId = await getUpkeepID(tx) + await registry.connect(admin).addFunds(testUpkeepId, toWei('100')) + + let performData = '0x' + for (let i = 0; i < maxPerformDataSize.toNumber(); i++) { + performData += '11' + } // max allowed performData + + await mock.setCanPerform(true) + await mock.setPerformGasToBurn(maxPerformGas) + + await getTransmitTx(registry, keeper1, [testUpkeepId], { + gasLimit: maxPerformGas.add(transmitGasOverhead), + numSigners: 11, + performDatas: [performData], + }) // Should not revert + }, + ) + + itMaybe( + 'performs upkeep, deducts payment, updates lastPerformed and emits events', + async () => { + await mock.setCanPerform(true) + + for (const i in fArray) { + const newF = fArray[i] + await registry + .connect(owner) + .setConfigTypeSafe( + signerAddresses, + keeperAddresses, + newF, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ) + const checkBlock = await ethers.provider.getBlock('latest') + + const keeperBefore = await registry.getTransmitterInfo( + await keeper1.getAddress(), + ) + const registrationBefore = await registry.getUpkeep(upkeepId) + const registryPremiumBefore = (await registry.getState()).state + .totalPremium + const keeperLinkBefore = await linkToken.balanceOf( + await keeper1.getAddress(), + ) + const registryLinkBefore = await linkToken.balanceOf( + registry.address, + ) + + // Do the thing + const tx = await getTransmitTx(registry, keeper1, [upkeepId], { + checkBlockNum: checkBlock.number, + checkBlockHash: checkBlock.hash, + numSigners: newF + 1, + }) + + const receipt = await tx.wait() + + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + // exactly 1 Upkeep Performed should be emitted + assert.equal(upkeepPerformedLogs.length, 1) + const upkeepPerformedLog = upkeepPerformedLogs[0] + + const id = upkeepPerformedLog.args.id + const success = upkeepPerformedLog.args.success + const trigger = upkeepPerformedLog.args.trigger + const gasUsed = upkeepPerformedLog.args.gasUsed + const gasOverhead = upkeepPerformedLog.args.gasOverhead + const totalPayment = upkeepPerformedLog.args.totalPayment + assert.equal(id.toString(), upkeepId.toString()) + assert.equal(success, true) + assert.equal( + trigger, + encodeBlockTrigger({ + blockNum: checkBlock.number, + blockHash: checkBlock.hash, + }), + ) + assert.isTrue(gasUsed.gt(BigNumber.from('0'))) + assert.isTrue(gasOverhead.gt(BigNumber.from('0'))) + assert.isTrue(totalPayment.gt(BigNumber.from('0'))) + + const keeperAfter = await registry.getTransmitterInfo( + await keeper1.getAddress(), + ) + const registrationAfter = await registry.getUpkeep(upkeepId) + const keeperLinkAfter = await linkToken.balanceOf( + await keeper1.getAddress(), + ) + const registryLinkAfter = await linkToken.balanceOf( + registry.address, + ) + const registryPremiumAfter = (await registry.getState()).state + .totalPremium + const premium = registryPremiumAfter.sub(registryPremiumBefore) + // Keeper payment is gasPayment + premium / num keepers + const keeperPayment = totalPayment + .sub(premium) + .add(premium.div(BigNumber.from(keeperAddresses.length))) + + assert.equal( + keeperAfter.balance.sub(keeperPayment).toString(), + keeperBefore.balance.toString(), + ) + assert.equal( + registrationBefore.balance.sub(totalPayment).toString(), + registrationAfter.balance.toString(), + ) + assert.isTrue(keeperLinkAfter.eq(keeperLinkBefore)) + assert.isTrue(registryLinkBefore.eq(registryLinkAfter)) + + // Amount spent should be updated correctly + assert.equal( + registrationAfter.amountSpent.sub(totalPayment).toString(), + registrationBefore.amountSpent.toString(), + ) + assert.isTrue( + registrationAfter.amountSpent + .sub(registrationBefore.amountSpent) + .eq(registrationBefore.balance.sub(registrationAfter.balance)), + ) + // Last perform block number should be updated + assert.equal( + registrationAfter.lastPerformedBlockNumber.toString(), + tx.blockNumber?.toString(), + ) + + // Latest epoch should be 5 + assert.equal((await registry.getState()).state.latestEpoch, 5) + } + }, + ) + + // describe.only('Gas benchmarking conditional upkeeps [ @skip-coverage ]', function () { + // const fs = [1] + // fs.forEach(function (newF) { + // it( + // 'When f=' + + // newF + + // ' calculates gas overhead appropriately within a margin for different scenarios', + // async () => { + // // Perform the upkeep once to remove non-zero storage slots and have predictable gas measurement + // let tx = await getTransmitTx(registry, keeper1, [upkeepId]) + // await tx.wait() + // + // await registry + // .connect(admin) + // .setUpkeepGasLimit(upkeepId, performGas.mul(3)) + // + // // Different test scenarios + // let longBytes = '0x' + // for (let i = 0; i < maxPerformDataSize.toNumber(); i++) { + // longBytes += '11' + // } + // const upkeepSuccessArray = [true, false] + // const performGasArray = [5000, performGas] + // const performDataArray = ['0x', longBytes] + // const chainModuleOverheads = await moduleBase.getGasOverhead() + // + // for (const i in upkeepSuccessArray) { + // for (const j in performGasArray) { + // for (const k in performDataArray) { + // const upkeepSuccess = upkeepSuccessArray[i] + // const performGas = performGasArray[j] + // const performData = performDataArray[k] + // + // await mock.setCanPerform(upkeepSuccess) + // await mock.setPerformGasToBurn(performGas) + // await registry + // .connect(owner) + // .setConfigTypeSafe( + // signerAddresses, + // keeperAddresses, + // newF, + // config, + // offchainVersion, + // offchainBytes, + // baseConfig[6], + // baseConfig[7], + // ) + // tx = await getTransmitTx(registry, keeper1, [upkeepId], { + // numSigners: newF + 1, + // performDatas: [performData], + // }) + // const receipt = await tx.wait() + // const upkeepPerformedLogs = + // parseUpkeepPerformedLogs(receipt) + // // exactly 1 Upkeep Performed should be emitted + // assert.equal(upkeepPerformedLogs.length, 1) + // const upkeepPerformedLog = upkeepPerformedLogs[0] + // + // const upkeepGasUsed = upkeepPerformedLog.args.gasUsed + // const chargedGasOverhead = + // upkeepPerformedLog.args.gasOverhead + // const actualGasOverhead = receipt.gasUsed + // .sub(upkeepGasUsed) + // .add(500000) // the amount of pubdataGas used returned by mock gas bound caller + // const estimatedGasOverhead = registryConditionalOverhead + // .add( + // registryPerSignerGasOverhead.mul( + // BigNumber.from(newF + 1), + // ), + // ) + // .add(chainModuleOverheads.chainModuleFixedOverhead) + // .add(65_400) + // + // assert.isTrue(upkeepGasUsed.gt(BigNumber.from('0'))) + // assert.isTrue(chargedGasOverhead.gt(BigNumber.from('0'))) + // assert.isTrue(actualGasOverhead.gt(BigNumber.from('0'))) + // + // console.log( + // 'Gas Benchmarking conditional upkeeps:', + // 'upkeepSuccess=', + // upkeepSuccess, + // 'performGas=', + // performGas.toString(), + // 'performData length=', + // performData.length / 2 - 1, + // 'sig verification ( f =', + // newF, + // '): estimated overhead: ', + // estimatedGasOverhead.toString(), // 179800 + // ' charged overhead: ', + // chargedGasOverhead.toString(), // 180560 + // ' actual overhead: ', + // actualGasOverhead.toString(), // 632949 + // ' calculation margin over gasUsed: ', + // chargedGasOverhead.sub(actualGasOverhead).toString(), // 18456 + // ' estimation margin over gasUsed: ', + // estimatedGasOverhead.sub(actualGasOverhead).toString(), // -27744 + // ' upkeepGasUsed: ', + // upkeepGasUsed, // 988620 + // ' receipt.gasUsed: ', + // receipt.gasUsed, // 1121569 + // ) + // + // // The actual gas overhead should be less than charged gas overhead, but not by a lot + // // The charged gas overhead is controlled by ACCOUNTING_FIXED_GAS_OVERHEAD and + // // ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD, and their correct values should be set to + // // satisfy constraints in multiple places + // assert.isTrue( + // chargedGasOverhead.gt(actualGasOverhead), + // 'Gas overhead calculated is too low, increase account gas variables (ACCOUNTING_FIXED_GAS_OVERHEAD/ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD) by at least ' + + // actualGasOverhead.sub(chargedGasOverhead).toString(), + // ) + // assert.isTrue( + // chargedGasOverhead // 180560 + // .sub(actualGasOverhead) // 132940 + // .lt(gasCalculationMargin), + // 'Gas overhead calculated is too high, decrease account gas variables (ACCOUNTING_FIXED_GAS_OVERHEAD/ACCOUNTING_PER_SIGNER_GAS_OVERHEAD) by at least ' + + // chargedGasOverhead + // .sub(actualGasOverhead) + // .sub(gasCalculationMargin) + // .toString(), + // ) + // + // // The estimated overhead during checkUpkeep should be close to the actual overhead in transaction + // // It should be greater than the actual overhead but not by a lot + // // The estimated overhead is controlled by variables + // // REGISTRY_CONDITIONAL_OVERHEAD, REGISTRY_LOG_OVERHEAD, REGISTRY_PER_SIGNER_GAS_OVERHEAD + // // REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD + // assert.isTrue( + // estimatedGasOverhead.gt(actualGasOverhead), + // 'Gas overhead estimated in check upkeep is too low, increase estimation gas variables (REGISTRY_CONDITIONAL_OVERHEAD/REGISTRY_LOG_OVERHEAD/REGISTRY_PER_SIGNER_GAS_OVERHEAD/REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD) by at least ' + + // estimatedGasOverhead.sub(chargedGasOverhead).toString(), + // ) + // assert.isTrue( + // estimatedGasOverhead + // .sub(actualGasOverhead) + // .lt(gasEstimationMargin), + // 'Gas overhead estimated is too high, decrease estimation gas variables (REGISTRY_CONDITIONAL_OVERHEAD/REGISTRY_LOG_OVERHEAD/REGISTRY_PER_SIGNER_GAS_OVERHEAD/REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD) by at least ' + + // estimatedGasOverhead + // .sub(actualGasOverhead) + // .sub(gasEstimationMargin) + // .toString(), + // ) + // } + // } + // } + // }, + // ) + // }) + // }) + + // describe.only('Gas benchmarking log upkeeps [ @skip-coverage ]', function () { + // const fs = [1] + // fs.forEach(function (newF) { + // it( + // 'When f=' + + // newF + + // ' calculates gas overhead appropriately within a margin', + // async () => { + // // Perform the upkeep once to remove non-zero storage slots and have predictable gas measurement + // let tx = await getTransmitTx(registry, keeper1, [logUpkeepId]) + // await tx.wait() + // const performData = '0x' + // await mock.setCanPerform(true) + // await mock.setPerformGasToBurn(performGas) + // await registry.setConfigTypeSafe( + // signerAddresses, + // keeperAddresses, + // newF, + // config, + // offchainVersion, + // offchainBytes, + // baseConfig[6], + // baseConfig[7], + // ) + // tx = await getTransmitTx(registry, keeper1, [logUpkeepId], { + // numSigners: newF + 1, + // performDatas: [performData], + // }) + // const receipt = await tx.wait() + // const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + // // exactly 1 Upkeep Performed should be emitted + // assert.equal(upkeepPerformedLogs.length, 1) + // const upkeepPerformedLog = upkeepPerformedLogs[0] + // const chainModuleOverheads = await moduleBase.getGasOverhead() + // + // const upkeepGasUsed = upkeepPerformedLog.args.gasUsed + // const chargedGasOverhead = upkeepPerformedLog.args.gasOverhead + // const actualGasOverhead = receipt.gasUsed + // .sub(upkeepGasUsed) + // .add(500000) // the amount of pubdataGas used returned by mock gas bound caller + // const estimatedGasOverhead = registryLogOverhead + // .add(registryPerSignerGasOverhead.mul(BigNumber.from(newF + 1))) + // .add(chainModuleOverheads.chainModuleFixedOverhead) + // .add(65_400) + // + // assert.isTrue(upkeepGasUsed.gt(BigNumber.from('0'))) + // assert.isTrue(chargedGasOverhead.gt(BigNumber.from('0'))) + // assert.isTrue(actualGasOverhead.gt(BigNumber.from('0'))) + // + // console.log( + // 'Gas Benchmarking log upkeeps:', + // 'upkeepSuccess=', + // true, + // 'performGas=', + // performGas.toString(), + // 'performData length=', + // performData.length / 2 - 1, + // 'sig verification ( f =', + // newF, + // '): estimated overhead: ', + // estimatedGasOverhead.toString(), + // ' charged overhead: ', + // chargedGasOverhead.toString(), + // ' actual overhead: ', + // actualGasOverhead.toString(), + // ' calculation margin over gasUsed: ', + // chargedGasOverhead.sub(actualGasOverhead).toString(), + // ' estimation margin over gasUsed: ', + // estimatedGasOverhead.sub(actualGasOverhead).toString(), + // ' upkeepGasUsed: ', + // upkeepGasUsed, + // ' receipt.gasUsed: ', + // receipt.gasUsed, + // ) + // + // assert.isTrue( + // chargedGasOverhead.gt(actualGasOverhead), + // 'Gas overhead calculated is too low, increase account gas variables (ACCOUNTING_FIXED_GAS_OVERHEAD/ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD) by at least ' + + // actualGasOverhead.sub(chargedGasOverhead).toString(), + // ) + // assert.isTrue( + // chargedGasOverhead + // .sub(actualGasOverhead) + // .lt(gasCalculationMargin), + // 'Gas overhead calculated is too high, decrease account gas variables (ACCOUNTING_FIXED_GAS_OVERHEAD/ACCOUNTING_PER_SIGNER_GAS_OVERHEAD) by at least ' + + // chargedGasOverhead + // .sub(actualGasOverhead) + // .sub(gasCalculationMargin) + // .toString(), + // ) + // + // assert.isTrue( + // estimatedGasOverhead.gt(actualGasOverhead), + // 'Gas overhead estimated in check upkeep is too low, increase estimation gas variables (REGISTRY_CONDITIONAL_OVERHEAD/REGISTRY_LOG_OVERHEAD/REGISTRY_PER_SIGNER_GAS_OVERHEAD/REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD) by at least ' + + // estimatedGasOverhead.sub(chargedGasOverhead).toString(), + // ) + // assert.isTrue( + // estimatedGasOverhead + // .sub(actualGasOverhead) + // .lt(gasEstimationMargin), + // 'Gas overhead estimated is too high, decrease estimation gas variables (REGISTRY_CONDITIONAL_OVERHEAD/REGISTRY_LOG_OVERHEAD/REGISTRY_PER_SIGNER_GAS_OVERHEAD/REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD) by at least ' + + // estimatedGasOverhead + // .sub(actualGasOverhead) + // .sub(gasEstimationMargin) + // .toString(), + // ) + // }, + // ) + // }) + // }) + }) + }) + + describeMaybe( + '#transmit with upkeep batches [ @skip-coverage ]', + function () { + const numPassingConditionalUpkeepsArray = [0, 1, 5] + const numPassingLogUpkeepsArray = [0, 1, 5] + const numFailingUpkeepsArray = [0, 3] + + for (let idx = 0; idx < numPassingConditionalUpkeepsArray.length; idx++) { + for (let jdx = 0; jdx < numPassingLogUpkeepsArray.length; jdx++) { + for (let kdx = 0; kdx < numFailingUpkeepsArray.length; kdx++) { + const numPassingConditionalUpkeeps = + numPassingConditionalUpkeepsArray[idx] + const numPassingLogUpkeeps = numPassingLogUpkeepsArray[jdx] + const numFailingUpkeeps = numFailingUpkeepsArray[kdx] + if ( + numPassingConditionalUpkeeps == 0 && + numPassingLogUpkeeps == 0 + ) { + continue + } + it( + '[Conditional:' + + numPassingConditionalUpkeeps + + ',Log:' + + numPassingLogUpkeeps + + ',Failures:' + + numFailingUpkeeps + + '] performs successful upkeeps and does not charge failing upkeeps', + async () => { + const allUpkeeps = await getMultipleUpkeepsDeployedAndFunded( + numPassingConditionalUpkeeps, + numPassingLogUpkeeps, + numFailingUpkeeps, + ) + const passingConditionalUpkeepIds = + allUpkeeps.passingConditionalUpkeepIds + const passingLogUpkeepIds = allUpkeeps.passingLogUpkeepIds + const failingUpkeepIds = allUpkeeps.failingUpkeepIds + + const keeperBefore = await registry.getTransmitterInfo( + await keeper1.getAddress(), + ) + const keeperLinkBefore = await linkToken.balanceOf( + await keeper1.getAddress(), + ) + const registryLinkBefore = await linkToken.balanceOf( + registry.address, + ) + const registryPremiumBefore = (await registry.getState()).state + .totalPremium + const registrationConditionalPassingBefore = await Promise.all( + passingConditionalUpkeepIds.map(async (id) => { + const reg = await registry.getUpkeep(BigNumber.from(id)) + assert.equal(reg.lastPerformedBlockNumber.toString(), '0') + return reg + }), + ) + const registrationLogPassingBefore = await Promise.all( + passingLogUpkeepIds.map(async (id) => { + const reg = await registry.getUpkeep(BigNumber.from(id)) + assert.equal(reg.lastPerformedBlockNumber.toString(), '0') + return reg + }), + ) + const registrationFailingBefore = await Promise.all( + failingUpkeepIds.map(async (id) => { + const reg = await registry.getUpkeep(BigNumber.from(id)) + assert.equal(reg.lastPerformedBlockNumber.toString(), '0') + return reg + }), + ) + + // cancel upkeeps so they will fail in the transmit process + // must call the cancel upkeep as the owner to avoid the CANCELLATION_DELAY + for (let ldx = 0; ldx < failingUpkeepIds.length; ldx++) { + await registry + .connect(owner) + .cancelUpkeep(failingUpkeepIds[ldx]) + } + + const tx = await getTransmitTx( + registry, + keeper1, + passingConditionalUpkeepIds.concat( + passingLogUpkeepIds.concat(failingUpkeepIds), + ), + ) + + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + // exactly numPassingUpkeeps Upkeep Performed should be emitted + assert.equal( + upkeepPerformedLogs.length, + numPassingConditionalUpkeeps + numPassingLogUpkeeps, + ) + const cancelledUpkeepReportLogs = + parseCancelledUpkeepReportLogs(receipt) + // exactly numFailingUpkeeps Upkeep Performed should be emitted + assert.equal( + cancelledUpkeepReportLogs.length, + numFailingUpkeeps, + ) + + const keeperAfter = await registry.getTransmitterInfo( + await keeper1.getAddress(), + ) + const keeperLinkAfter = await linkToken.balanceOf( + await keeper1.getAddress(), + ) + const registryLinkAfter = await linkToken.balanceOf( + registry.address, + ) + const registrationConditionalPassingAfter = await Promise.all( + passingConditionalUpkeepIds.map(async (id) => { + return await registry.getUpkeep(BigNumber.from(id)) + }), + ) + const registrationLogPassingAfter = await Promise.all( + passingLogUpkeepIds.map(async (id) => { + return await registry.getUpkeep(BigNumber.from(id)) + }), + ) + const registrationFailingAfter = await Promise.all( + failingUpkeepIds.map(async (id) => { + return await registry.getUpkeep(BigNumber.from(id)) + }), + ) + const registryPremiumAfter = (await registry.getState()).state + .totalPremium + const premium = registryPremiumAfter.sub(registryPremiumBefore) + + let netPayment = BigNumber.from('0') + for (let i = 0; i < numPassingConditionalUpkeeps; i++) { + const id = upkeepPerformedLogs[i].args.id + const gasUsed = upkeepPerformedLogs[i].args.gasUsed + const gasOverhead = upkeepPerformedLogs[i].args.gasOverhead + const totalPayment = upkeepPerformedLogs[i].args.totalPayment + + expect(id).to.equal(passingConditionalUpkeepIds[i]) + assert.isTrue(gasUsed.gt(BigNumber.from('0'))) + assert.isTrue(gasOverhead.gt(BigNumber.from('0'))) + assert.isTrue(totalPayment.gt(BigNumber.from('0'))) + + // Balance should be deducted + assert.equal( + registrationConditionalPassingBefore[i].balance + .sub(totalPayment) + .toString(), + registrationConditionalPassingAfter[i].balance.toString(), + ) + + // Amount spent should be updated correctly + assert.equal( + registrationConditionalPassingAfter[i].amountSpent + .sub(totalPayment) + .toString(), + registrationConditionalPassingBefore[ + i + ].amountSpent.toString(), + ) + + // Last perform block number should be updated + assert.equal( + registrationConditionalPassingAfter[ + i + ].lastPerformedBlockNumber.toString(), + tx.blockNumber?.toString(), + ) + + netPayment = netPayment.add(totalPayment) + } + + for (let i = 0; i < numPassingLogUpkeeps; i++) { + const id = + upkeepPerformedLogs[numPassingConditionalUpkeeps + i].args + .id + const gasUsed = + upkeepPerformedLogs[numPassingConditionalUpkeeps + i].args + .gasUsed + const gasOverhead = + upkeepPerformedLogs[numPassingConditionalUpkeeps + i].args + .gasOverhead + const totalPayment = + upkeepPerformedLogs[numPassingConditionalUpkeeps + i].args + .totalPayment + + expect(id).to.equal(passingLogUpkeepIds[i]) + assert.isTrue(gasUsed.gt(BigNumber.from('0'))) + assert.isTrue(gasOverhead.gt(BigNumber.from('0'))) + assert.isTrue(totalPayment.gt(BigNumber.from('0'))) + + // Balance should be deducted + assert.equal( + registrationLogPassingBefore[i].balance + .sub(totalPayment) + .toString(), + registrationLogPassingAfter[i].balance.toString(), + ) + + // Amount spent should be updated correctly + assert.equal( + registrationLogPassingAfter[i].amountSpent + .sub(totalPayment) + .toString(), + registrationLogPassingBefore[i].amountSpent.toString(), + ) + + // Last perform block number should not be updated for log triggers + assert.equal( + registrationLogPassingAfter[ + i + ].lastPerformedBlockNumber.toString(), + '0', + ) + + netPayment = netPayment.add(totalPayment) + } + + for (let i = 0; i < numFailingUpkeeps; i++) { + // CancelledUpkeep log should be emitted + const id = cancelledUpkeepReportLogs[i].args.id + expect(id).to.equal(failingUpkeepIds[i]) + + // Balance and amount spent should be same + assert.equal( + registrationFailingBefore[i].balance.toString(), + registrationFailingAfter[i].balance.toString(), + ) + assert.equal( + registrationFailingBefore[i].amountSpent.toString(), + registrationFailingAfter[i].amountSpent.toString(), + ) + + // Last perform block number should not be updated + assert.equal( + registrationFailingAfter[ + i + ].lastPerformedBlockNumber.toString(), + '0', + ) + } + + // Keeper payment is gasPayment + premium / num keepers + const keeperPayment = netPayment + .sub(premium) + .add(premium.div(BigNumber.from(keeperAddresses.length))) + + // Keeper should be paid net payment for all passed upkeeps + assert.equal( + keeperAfter.balance.sub(keeperPayment).toString(), + keeperBefore.balance.toString(), + ) + + assert.isTrue(keeperLinkAfter.eq(keeperLinkBefore)) + assert.isTrue(registryLinkBefore.eq(registryLinkAfter)) + }, + ) + + it( + '[Conditional:' + + numPassingConditionalUpkeeps + + ',Log' + + numPassingLogUpkeeps + + ',Failures:' + + numFailingUpkeeps + + '] splits gas overhead appropriately among performed upkeeps [ @skip-coverage ]', + async () => { + const allUpkeeps = await getMultipleUpkeepsDeployedAndFunded( + numPassingConditionalUpkeeps, + numPassingLogUpkeeps, + numFailingUpkeeps, + ) + const passingConditionalUpkeepIds = + allUpkeeps.passingConditionalUpkeepIds + const passingLogUpkeepIds = allUpkeeps.passingLogUpkeepIds + const failingUpkeepIds = allUpkeeps.failingUpkeepIds + + // Perform the upkeeps once to remove non-zero storage slots and have predictable gas measurement + let tx = await getTransmitTx( + registry, + keeper1, + passingConditionalUpkeepIds.concat( + passingLogUpkeepIds.concat(failingUpkeepIds), + ), + ) + + await tx.wait() + + // cancel upkeeps so they will fail in the transmit process + // must call the cancel upkeep as the owner to avoid the CANCELLATION_DELAY + for (let ldx = 0; ldx < failingUpkeepIds.length; ldx++) { + await registry + .connect(owner) + .cancelUpkeep(failingUpkeepIds[ldx]) + } + + // Do the actual thing + + tx = await getTransmitTx( + registry, + keeper1, + passingConditionalUpkeepIds.concat( + passingLogUpkeepIds.concat(failingUpkeepIds), + ), + ) + + const receipt = await tx.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + // exactly numPassingUpkeeps Upkeep Performed should be emitted + assert.equal( + upkeepPerformedLogs.length, + numPassingConditionalUpkeeps + numPassingLogUpkeeps, + ) + + let netGasUsedPlusChargedOverhead = BigNumber.from('0') + for (let i = 0; i < numPassingConditionalUpkeeps; i++) { + const gasUsed = upkeepPerformedLogs[i].args.gasUsed + const chargedGasOverhead = + upkeepPerformedLogs[i].args.gasOverhead + + assert.isTrue(gasUsed.gt(BigNumber.from('0'))) + assert.isTrue(chargedGasOverhead.gt(BigNumber.from('0'))) + + // Overhead should be same for every upkeep + assert.isTrue( + chargedGasOverhead.eq( + upkeepPerformedLogs[0].args.gasOverhead, + ), + ) + netGasUsedPlusChargedOverhead = netGasUsedPlusChargedOverhead + .add(gasUsed) + .add(chargedGasOverhead) + } + + for (let i = 0; i < numPassingLogUpkeeps; i++) { + const gasUsed = + upkeepPerformedLogs[numPassingConditionalUpkeeps + i].args + .gasUsed + const chargedGasOverhead = + upkeepPerformedLogs[numPassingConditionalUpkeeps + i].args + .gasOverhead + + assert.isTrue(gasUsed.gt(BigNumber.from('0'))) + assert.isTrue(chargedGasOverhead.gt(BigNumber.from('0'))) + + // Overhead should be same for every upkeep + assert.isTrue( + chargedGasOverhead.eq( + upkeepPerformedLogs[numPassingConditionalUpkeeps].args + .gasOverhead, + ), + ) + netGasUsedPlusChargedOverhead = netGasUsedPlusChargedOverhead + .add(gasUsed) + .add(chargedGasOverhead) + } + + console.log( + 'Gas Benchmarking - batching (passedConditionalUpkeeps: ', + numPassingConditionalUpkeeps, + 'passedLogUpkeeps:', + numPassingLogUpkeeps, + 'failedUpkeeps:', + numFailingUpkeeps, + '): ', + numPassingConditionalUpkeeps > 0 + ? 'charged conditional overhead' + : '', + numPassingConditionalUpkeeps > 0 + ? upkeepPerformedLogs[0].args.gasOverhead.toString() + : '', + numPassingLogUpkeeps > 0 ? 'charged log overhead' : '', + numPassingLogUpkeeps > 0 + ? upkeepPerformedLogs[ + numPassingConditionalUpkeeps + ].args.gasOverhead.toString() + : '', + ' margin over gasUsed', + netGasUsedPlusChargedOverhead.sub(receipt.gasUsed).toString(), + ) + + // The total gas charged should be greater than tx gas + assert.isTrue( + netGasUsedPlusChargedOverhead.gt(receipt.gasUsed), + 'Charged gas overhead is too low for batch upkeeps, increase ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD', + ) + }, + ) + } + } + } + + it('has enough perform gas overhead for large batches [ @skip-coverage ]', async () => { + const numUpkeeps = 20 + const upkeepIds: BigNumber[] = [] + let totalPerformGas = BigNumber.from('0') + for (let i = 0; i < numUpkeeps; i++) { + const mock = await upkeepMockFactory.deploy() + const tx = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + const testUpkeepId = await getUpkeepID(tx) + upkeepIds.push(testUpkeepId) + + // Add funds to passing upkeeps + await registry.connect(owner).addFunds(testUpkeepId, toWei('10')) + + await mock.setCanPerform(true) + await mock.setPerformGasToBurn(performGas) + + totalPerformGas = totalPerformGas.add(performGas) + } + + // Should revert with no overhead added + await evmRevert( + getTransmitTx(registry, keeper1, upkeepIds, { + gasLimit: totalPerformGas, + }), + ) + // Should not revert with overhead added + await getTransmitTx(registry, keeper1, upkeepIds, { + gasLimit: totalPerformGas.add(transmitGasOverhead), + }) + }) + }, + ) + + describe('#recoverFunds', () => { + const sent = toWei('7') + + beforeEach(async () => { + await linkToken.connect(admin).approve(registry.address, toWei('100')) + await linkToken + .connect(owner) + .transfer(await keeper1.getAddress(), toWei('1000')) + + // add funds to upkeep 1 and perform and withdraw some payment + const tx = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + + const id1 = await getUpkeepID(tx) + await registry.connect(admin).addFunds(id1, toWei('5')) + + await getTransmitTx(registry, keeper1, [id1]) + await getTransmitTx(registry, keeper2, [id1]) + await getTransmitTx(registry, keeper3, [id1]) + + await registry + .connect(payee1) + .withdrawPayment( + await keeper1.getAddress(), + await nonkeeper.getAddress(), + ) + + // transfer funds directly to the registry + await linkToken.connect(keeper1).transfer(registry.address, sent) + + // add funds to upkeep 2 and perform and withdraw some payment + const tx2 = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + const id2 = await getUpkeepID(tx2) + await registry.connect(admin).addFunds(id2, toWei('5')) + + await getTransmitTx(registry, keeper1, [id2]) + await getTransmitTx(registry, keeper2, [id2]) + await getTransmitTx(registry, keeper3, [id2]) + + await registry + .connect(payee2) + .withdrawPayment( + await keeper2.getAddress(), + await nonkeeper.getAddress(), + ) + + // transfer funds using onTokenTransfer + const data = ethers.utils.defaultAbiCoder.encode(['uint256'], [id2]) + await linkToken + .connect(owner) + .transferAndCall(registry.address, toWei('1'), data) + + // withdraw some funds + await registry.connect(owner).cancelUpkeep(id1) + await registry + .connect(admin) + .withdrawFunds(id1, await nonkeeper.getAddress()) + }) + }) + + describe('#getMinBalanceForUpkeep / #checkUpkeep / #transmit', () => { + it('calculates the minimum balance appropriately', async () => { + await mock.setCanCheck(true) + + const oneWei = BigNumber.from(1) + const minBalance = await registry.getMinBalanceForUpkeep(upkeepId) + const tooLow = minBalance.sub(oneWei) + + await registry.connect(admin).addFunds(upkeepId, tooLow) + let checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.INSUFFICIENT_BALANCE, + ) + + await registry.connect(admin).addFunds(upkeepId, oneWei) + checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + assert.equal(checkUpkeepResult.upkeepNeeded, true) + }) + + it('uses maxPerformData size in checkUpkeep but actual performDataSize in transmit', async () => { + const tx = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + const upkeepID = await getUpkeepID(tx) + await mock.setCanCheck(true) + await mock.setCanPerform(true) + + // upkeep is underfunded by 1 wei + const minBalance1 = (await registry.getMinBalanceForUpkeep(upkeepID)).sub( + 1, + ) + await registry.connect(owner).addFunds(upkeepID, minBalance1) + + // upkeep check should return false, 2 should return true + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepID) + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.INSUFFICIENT_BALANCE, + ) + + // however upkeep should perform and pay all the remaining balance + let maxPerformData = '0x' + for (let i = 0; i < maxPerformDataSize.toNumber(); i++) { + maxPerformData += '11' + } + + const tx2 = await getTransmitTx(registry, keeper1, [upkeepID], { + gasPrice: gasWei.mul(gasCeilingMultiplier), + performDatas: [maxPerformData], + }) + + const receipt = await tx2.wait() + const upkeepPerformedLogs = parseUpkeepPerformedLogs(receipt) + assert.equal(upkeepPerformedLogs.length, 1) + }) + }) + + describe('#withdrawFunds', () => { + let upkeepId2: BigNumber + + beforeEach(async () => { + const tx = await registry + .connect(owner) + .registerUpkeep( + mock.address, + performGas, + await admin.getAddress(), + Trigger.CONDITION, + linkToken.address, + '0x', + '0x', + '0x', + ) + upkeepId2 = await getUpkeepID(tx) + + await registry.connect(admin).addFunds(upkeepId, toWei('100')) + await registry.connect(admin).addFunds(upkeepId2, toWei('100')) + + // Do a perform so that upkeep is charged some amount + await getTransmitTx(registry, keeper1, [upkeepId]) + await getTransmitTx(registry, keeper1, [upkeepId2]) + }) + + describe('after the registration is paused, then cancelled', () => { + it('allows the admin to withdraw', async () => { + const balance = await registry.getBalance(upkeepId) + const payee = await payee1.getAddress() + await registry.connect(admin).pauseUpkeep(upkeepId) + await registry.connect(owner).cancelUpkeep(upkeepId) + await expect(() => + registry.connect(admin).withdrawFunds(upkeepId, payee), + ).to.changeTokenBalance(linkToken, payee1, balance) + }) + }) + + describe('after the registration is cancelled', () => { + beforeEach(async () => { + await registry.connect(owner).cancelUpkeep(upkeepId) + await registry.connect(owner).cancelUpkeep(upkeepId2) + }) + + it('can be called successively on two upkeeps', async () => { + await registry + .connect(admin) + .withdrawFunds(upkeepId, await payee1.getAddress()) + await registry + .connect(admin) + .withdrawFunds(upkeepId2, await payee1.getAddress()) + }) + + it('moves the funds out and updates the balance and emits an event', async () => { + const payee1Before = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const registryBefore = await linkToken.balanceOf(registry.address) + + let registration = await registry.getUpkeep(upkeepId) + const previousBalance = registration.balance + + const tx = await registry + .connect(admin) + .withdrawFunds(upkeepId, await payee1.getAddress()) + await expect(tx) + .to.emit(registry, 'FundsWithdrawn') + .withArgs(upkeepId, previousBalance, await payee1.getAddress()) + + const payee1After = await linkToken.balanceOf(await payee1.getAddress()) + const registryAfter = await linkToken.balanceOf(registry.address) + + assert.isTrue(payee1Before.add(previousBalance).eq(payee1After)) + assert.isTrue(registryBefore.sub(previousBalance).eq(registryAfter)) + + registration = await registry.getUpkeep(upkeepId) + assert.equal(registration.balance.toNumber(), 0) + }) + }) + }) + + describe('#simulatePerformUpkeep', () => { + it('reverts if called by non zero address', async () => { + await evmRevertCustomError( + registry + .connect(await owner.getAddress()) + .callStatic.simulatePerformUpkeep(upkeepId, '0x'), + registry, + 'OnlySimulatedBackend', + ) + }) + + it('reverts when registry is paused', async () => { + await registry.connect(owner).pause() + await evmRevertCustomError( + registry + .connect(zeroAddress) + .callStatic.simulatePerformUpkeep(upkeepId, '0x'), + registry, + 'RegistryPaused', + ) + }) + + it('returns false and gasUsed when perform fails', async () => { + await mock.setCanPerform(false) + + const simulatePerformResult = await registry + .connect(zeroAddress) + .callStatic.simulatePerformUpkeep(upkeepId, '0x') + + assert.equal(simulatePerformResult.success, false) + assert.isTrue(simulatePerformResult.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + }) + + it('returns true, gasUsed, and performGas when perform succeeds', async () => { + await mock.setCanPerform(true) + + const simulatePerformResult = await registry + .connect(zeroAddress) + .callStatic.simulatePerformUpkeep(upkeepId, '0x') + + assert.equal(simulatePerformResult.success, true) + assert.isTrue(simulatePerformResult.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + }) + + it('returns correct amount of gasUsed when perform succeeds', async () => { + await mock.setCanPerform(true) + await mock.setPerformGasToBurn(performGas) // 1,000,000 + + // increase upkeep gas limit because the mock gas bound caller will always return 500,000 as the L1 gas used + // that brings the total gas used to about 1M + 0.5M = 1.5M + await registry + .connect(admin) + .setUpkeepGasLimit(upkeepId, BigNumber.from(2000000)) + + const simulatePerformResult = await registry + .connect(zeroAddress) + .callStatic.simulatePerformUpkeep(upkeepId, '0x') + + // Full execute gas should be used, with some performGasBuffer(1000) + assert.isTrue( + simulatePerformResult.gasUsed.gt( + performGas.add(pubdataGas).sub(BigNumber.from('1000')), + ), + ) + }) + }) + + describe('#checkUpkeep', () => { + it('reverts if called by non zero address', async () => { + await evmRevertCustomError( + registry + .connect(await owner.getAddress()) + .callStatic['checkUpkeep(uint256)'](upkeepId), + registry, + 'OnlySimulatedBackend', + ) + }) + + it('returns false and error code if the upkeep is cancelled by admin', async () => { + await registry.connect(admin).cancelUpkeep(upkeepId) + + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal(checkUpkeepResult.performData, '0x') + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.UPKEEP_CANCELLED, + ) + expect(checkUpkeepResult.gasUsed).to.equal(0) + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + }) + + it('returns false and error code if the upkeep is cancelled by owner', async () => { + await registry.connect(owner).cancelUpkeep(upkeepId) + + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal(checkUpkeepResult.performData, '0x') + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.UPKEEP_CANCELLED, + ) + expect(checkUpkeepResult.gasUsed).to.equal(0) + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + }) + + it('returns false and error code if the registry is paused', async () => { + await registry.connect(owner).pause() + + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal(checkUpkeepResult.performData, '0x') + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.REGISTRY_PAUSED, + ) + expect(checkUpkeepResult.gasUsed).to.equal(0) + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + }) + + it('returns false and error code if the upkeep is paused', async () => { + await registry.connect(admin).pauseUpkeep(upkeepId) + + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal(checkUpkeepResult.performData, '0x') + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.UPKEEP_PAUSED, + ) + expect(checkUpkeepResult.gasUsed).to.equal(0) + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + }) + + it('returns false and error code if user is out of funds', async () => { + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal(checkUpkeepResult.performData, '0x') + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.INSUFFICIENT_BALANCE, + ) + expect(checkUpkeepResult.gasUsed).to.equal(0) + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + }) + + context('when the registration is funded', () => { + beforeEach(async () => { + await linkToken.connect(admin).approve(registry.address, toWei('200')) + await registry.connect(admin).addFunds(upkeepId, toWei('100')) + await registry.connect(admin).addFunds(logUpkeepId, toWei('100')) + }) + + it('returns false, error code, and revert data if the target check reverts', async () => { + await mock.setShouldRevertCheck(true) + await mock.setCheckRevertReason( + 'custom revert error, clever way to insert offchain data', + ) + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + assert.equal(checkUpkeepResult.upkeepNeeded, false) + + const revertReasonBytes = `0x${checkUpkeepResult.performData.slice(10)}` // remove sighash + assert.equal( + ethers.utils.defaultAbiCoder.decode(['string'], revertReasonBytes)[0], + 'custom revert error, clever way to insert offchain data', + ) + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.TARGET_CHECK_REVERTED, + ) + assert.isTrue(checkUpkeepResult.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + // Feed data should be returned here + assert.isTrue(checkUpkeepResult.fastGasWei.gt(BigNumber.from('0'))) + assert.isTrue(checkUpkeepResult.linkUSD.gt(BigNumber.from('0'))) + }) + + it('returns false, error code, and no revert data if the target check revert data exceeds maxRevertDataSize', async () => { + await mock.setShouldRevertCheck(true) + let longRevertReason = '' + for (let i = 0; i <= maxRevertDataSize.toNumber(); i++) { + longRevertReason += 'x' + } + await mock.setCheckRevertReason(longRevertReason) + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + assert.equal(checkUpkeepResult.upkeepNeeded, false) + + assert.equal(checkUpkeepResult.performData, '0x') + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.REVERT_DATA_EXCEEDS_LIMIT, + ) + assert.isTrue(checkUpkeepResult.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + }) + + it('returns false and error code if the upkeep is not needed', async () => { + await mock.setCanCheck(false) + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal(checkUpkeepResult.performData, '0x') + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.UPKEEP_NOT_NEEDED, + ) + assert.isTrue(checkUpkeepResult.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + }) + + it('returns false and error code if the performData exceeds limit', async () => { + let longBytes = '0x' + for (let i = 0; i < 5000; i++) { + longBytes += '1' + } + await mock.setCanCheck(true) + await mock.setPerformData(longBytes) + + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId) + + assert.equal(checkUpkeepResult.upkeepNeeded, false) + assert.equal(checkUpkeepResult.performData, '0x') + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT, + ) + assert.isTrue(checkUpkeepResult.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + }) + + it('returns true with gas used if the target can execute', async () => { + await mock.setCanCheck(true) + await mock.setPerformData(randomBytes) + + const latestBlock = await ethers.provider.getBlock('latest') + + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId, { + blockTag: latestBlock.number, + }) + + assert.equal(checkUpkeepResult.upkeepNeeded, true) + assert.equal(checkUpkeepResult.performData, randomBytes) + assert.equal( + checkUpkeepResult.upkeepFailureReason, + UpkeepFailureReason.NONE, + ) + assert.isTrue(checkUpkeepResult.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + expect(checkUpkeepResult.gasLimit).to.equal(performGas) + assert.isTrue(checkUpkeepResult.fastGasWei.eq(gasWei)) + assert.isTrue(checkUpkeepResult.linkUSD.eq(linkUSD)) + }) + + it('calls checkLog for log-trigger upkeeps', async () => { + const log: Log = { + index: 0, + timestamp: 0, + txHash: ethers.utils.randomBytes(32), + blockNumber: 100, + blockHash: ethers.utils.randomBytes(32), + source: randomAddress(), + topics: [ethers.utils.randomBytes(32), ethers.utils.randomBytes(32)], + data: ethers.utils.randomBytes(1000), + } + + await ltUpkeep.mock.checkLog.withArgs(log, '0x').returns(true, '0x1234') + + const checkData = encodeLog(log) + + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256,bytes)'](logUpkeepId, checkData) + + expect(checkUpkeepResult.upkeepNeeded).to.be.true + expect(checkUpkeepResult.performData).to.equal('0x1234') + }) + + itMaybe( + 'has a large enough gas overhead to cover upkeeps that use all their gas [ @skip-coverage ]', + async () => { + await mock.setCanCheck(true) + await mock.setCheckGasToBurn(checkGasLimit) + const gas = checkGasLimit.add(checkGasOverhead) + const checkUpkeepResult = await registry + .connect(zeroAddress) + .callStatic['checkUpkeep(uint256)'](upkeepId, { + gasLimit: gas, + }) + + assert.equal(checkUpkeepResult.upkeepNeeded, true) + }, + ) + }) + }) + + describe('#getMaxPaymentForGas', () => { + itMaybe('calculates the max fee appropriately in ZKSync', async () => { + await verifyMaxPayment(registry, moduleBase) + }) + + it('uses the fallback gas price if the feed has issues in ZKSync', async () => { + const chainModuleOverheads = await moduleBase.getGasOverhead() + const expectedFallbackMaxPayment = linkForGas( + performGas, + registryConditionalOverhead + .add(registryPerSignerGasOverhead.mul(f + 1)) + .add(chainModuleOverheads.chainModuleFixedOverhead), + gasCeilingMultiplier.mul('2'), // fallbackGasPrice is 2x gas price + paymentPremiumPPB, + flatFeeMilliCents, + ).total + + // Stale feed + let roundId = 99 + const answer = 100 + let updatedAt = 946684800 // New Years 2000 🥳 + let startedAt = 946684799 + await gasPriceFeed + .connect(owner) + .updateRoundData(roundId, answer, updatedAt, startedAt) + + assert.equal( + expectedFallbackMaxPayment.toString(), + ( + await registry.getMaxPaymentForGas( + upkeepId, + Trigger.CONDITION, + performGas, + linkToken.address, + ) + ).toString(), + ) + + // Negative feed price + roundId = 100 + updatedAt = now() + startedAt = 946684799 + await gasPriceFeed + .connect(owner) + .updateRoundData(roundId, -100, updatedAt, startedAt) + + assert.equal( + expectedFallbackMaxPayment.toString(), + ( + await registry.getMaxPaymentForGas( + upkeepId, + Trigger.CONDITION, + performGas, + linkToken.address, + ) + ).toString(), + ) + + // Zero feed price + roundId = 101 + updatedAt = now() + startedAt = 946684799 + await gasPriceFeed + .connect(owner) + .updateRoundData(roundId, 0, updatedAt, startedAt) + + assert.equal( + expectedFallbackMaxPayment.toString(), + ( + await registry.getMaxPaymentForGas( + upkeepId, + Trigger.CONDITION, + performGas, + linkToken.address, + ) + ).toString(), + ) + }) + + it('uses the fallback link price if the feed has issues in ZKSync', async () => { + const chainModuleOverheads = await moduleBase.getGasOverhead() + const expectedFallbackMaxPayment = linkForGas( + performGas, + registryConditionalOverhead + .add(registryPerSignerGasOverhead.mul(f + 1)) + .add(chainModuleOverheads.chainModuleFixedOverhead), + gasCeilingMultiplier.mul('2'), // fallbackLinkPrice is 1/2 link price, so multiply by 2 + paymentPremiumPPB, + flatFeeMilliCents, + ).total + + // Stale feed + let roundId = 99 + const answer = 100 + let updatedAt = 946684800 // New Years 2000 🥳 + let startedAt = 946684799 + await linkUSDFeed + .connect(owner) + .updateRoundData(roundId, answer, updatedAt, startedAt) + + assert.equal( + expectedFallbackMaxPayment.toString(), + ( + await registry.getMaxPaymentForGas( + upkeepId, + Trigger.CONDITION, + performGas, + linkToken.address, + ) + ).toString(), + ) + + // Negative feed price + roundId = 100 + updatedAt = now() + startedAt = 946684799 + await linkUSDFeed + .connect(owner) + .updateRoundData(roundId, -100, updatedAt, startedAt) + + assert.equal( + expectedFallbackMaxPayment.toString(), + ( + await registry.getMaxPaymentForGas( + upkeepId, + Trigger.CONDITION, + performGas, + linkToken.address, + ) + ).toString(), + ) + + // Zero feed price + roundId = 101 + updatedAt = now() + startedAt = 946684799 + await linkUSDFeed + .connect(owner) + .updateRoundData(roundId, 0, updatedAt, startedAt) + + assert.equal( + expectedFallbackMaxPayment.toString(), + ( + await registry.getMaxPaymentForGas( + upkeepId, + Trigger.CONDITION, + performGas, + linkToken.address, + ) + ).toString(), + ) + }) + }) + + describe('#typeAndVersion', () => { + it('uses the correct type and version', async () => { + const typeAndVersion = await registry.typeAndVersion() + assert.equal(typeAndVersion, 'AutomationRegistry 2.3.0') + }) + }) + + describeMaybe('#setConfig - onchain', async () => { + const maxGas = BigNumber.from(6) + const staleness = BigNumber.from(4) + const ceiling = BigNumber.from(5) + const newMaxCheckDataSize = BigNumber.from(10000) + const newMaxPerformDataSize = BigNumber.from(10000) + const newMaxRevertDataSize = BigNumber.from(10000) + const newMaxPerformGas = BigNumber.from(10000000) + const fbGasEth = BigNumber.from(7) + const fbLinkEth = BigNumber.from(8) + const fbNativeEth = BigNumber.from(100) + const newTranscoder = randomAddress() + const newRegistrars = [randomAddress(), randomAddress()] + const upkeepManager = randomAddress() + const financeAdminAddress = randomAddress() + + const newConfig: OnChainConfig = { + checkGasLimit: maxGas, + stalenessSeconds: staleness, + gasCeilingMultiplier: ceiling, + maxCheckDataSize: newMaxCheckDataSize, + maxPerformDataSize: newMaxPerformDataSize, + maxRevertDataSize: newMaxRevertDataSize, + maxPerformGas: newMaxPerformGas, + fallbackGasPrice: fbGasEth, + fallbackLinkPrice: fbLinkEth, + fallbackNativePrice: fbNativeEth, + transcoder: newTranscoder, + registrars: newRegistrars, + upkeepPrivilegeManager: upkeepManager, + chainModule: moduleBase.address, + reorgProtectionEnabled: true, + financeAdmin: financeAdminAddress, + } + + it('reverts when called by anyone but the proposed owner', async () => { + await evmRevert( + registry + .connect(payee1) + .setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + newConfig, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + 'Only callable by owner', + ) + }) + + it('reverts if signers or transmitters are the zero address', async () => { + await evmRevertCustomError( + registry + .connect(owner) + .setConfigTypeSafe( + [randomAddress(), randomAddress(), randomAddress(), zeroAddress], + [ + randomAddress(), + randomAddress(), + randomAddress(), + randomAddress(), + ], + f, + newConfig, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + registry, + 'InvalidSigner', + ) + + await evmRevertCustomError( + registry + .connect(owner) + .setConfigTypeSafe( + [ + randomAddress(), + randomAddress(), + randomAddress(), + randomAddress(), + ], + [randomAddress(), randomAddress(), randomAddress(), zeroAddress], + f, + newConfig, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + registry, + 'InvalidTransmitter', + ) + }) + + it('updates the onchainConfig and configDigest', async () => { + const old = await registry.getState() + const oldConfig = await registry.getConfig() + const oldState = old.state + assert.isTrue(stalenessSeconds.eq(oldConfig.stalenessSeconds)) + assert.isTrue(gasCeilingMultiplier.eq(oldConfig.gasCeilingMultiplier)) + + await registry + .connect(owner) + .setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + newConfig, + offchainVersion, + offchainBytes, + [], + [], + ) + + const updated = await registry.getState() + const updatedConfig = updated.config + const updatedState = updated.state + assert.equal(updatedConfig.stalenessSeconds, staleness.toNumber()) + assert.equal(updatedConfig.gasCeilingMultiplier, ceiling.toNumber()) + assert.equal( + updatedConfig.maxCheckDataSize, + newMaxCheckDataSize.toNumber(), + ) + assert.equal( + updatedConfig.maxPerformDataSize, + newMaxPerformDataSize.toNumber(), + ) + assert.equal( + updatedConfig.maxRevertDataSize, + newMaxRevertDataSize.toNumber(), + ) + assert.equal(updatedConfig.maxPerformGas, newMaxPerformGas.toNumber()) + assert.equal(updatedConfig.checkGasLimit, maxGas.toNumber()) + assert.equal( + updatedConfig.fallbackGasPrice.toNumber(), + fbGasEth.toNumber(), + ) + assert.equal( + updatedConfig.fallbackLinkPrice.toNumber(), + fbLinkEth.toNumber(), + ) + assert.equal(updatedState.latestEpoch, 0) + + assert(oldState.configCount + 1 == updatedState.configCount) + assert( + oldState.latestConfigBlockNumber != + updatedState.latestConfigBlockNumber, + ) + assert(oldState.latestConfigDigest != updatedState.latestConfigDigest) + + assert.equal(updatedConfig.transcoder, newTranscoder) + assert.deepEqual(updatedConfig.registrars, newRegistrars) + assert.equal(updatedConfig.upkeepPrivilegeManager, upkeepManager) + }) + + it('maintains paused state when config is changed', async () => { + await registry.pause() + const old = await registry.getState() + assert.isTrue(old.state.paused) + + await registry + .connect(owner) + .setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + newConfig, + offchainVersion, + offchainBytes, + [], + [], + ) + + const updated = await registry.getState() + assert.isTrue(updated.state.paused) + }) + + it('emits an event', async () => { + const tx = await registry + .connect(owner) + .setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + newConfig, + offchainVersion, + offchainBytes, + [], + [], + ) + await expect(tx).to.emit(registry, 'ConfigSet') + }) + }) + + describe('#setConfig - offchain', () => { + let newKeepers: string[] + + beforeEach(async () => { + newKeepers = [ + await personas.Eddy.getAddress(), + await personas.Nick.getAddress(), + await personas.Neil.getAddress(), + await personas.Carol.getAddress(), + ] + }) + + it('reverts when called by anyone but the owner', async () => { + await evmRevert( + registry + .connect(payee1) + .setConfigTypeSafe( + newKeepers, + newKeepers, + f, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + 'Only callable by owner', + ) + }) + + it('reverts if too many keeperAddresses set', async () => { + for (let i = 0; i < 40; i++) { + newKeepers.push(randomAddress()) + } + await evmRevertCustomError( + registry + .connect(owner) + .setConfigTypeSafe( + newKeepers, + newKeepers, + f, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + registry, + 'TooManyOracles', + ) + }) + + it('reverts if f=0', async () => { + await evmRevertCustomError( + registry + .connect(owner) + .setConfigTypeSafe( + newKeepers, + newKeepers, + 0, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + registry, + 'IncorrectNumberOfFaultyOracles', + ) + }) + + it('reverts if signers != transmitters length', async () => { + const signers = [randomAddress()] + await evmRevertCustomError( + registry + .connect(owner) + .setConfigTypeSafe( + signers, + newKeepers, + f, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + registry, + 'IncorrectNumberOfSigners', + ) + }) + + it('reverts if signers <= 3f', async () => { + newKeepers.pop() + await evmRevertCustomError( + registry + .connect(owner) + .setConfigTypeSafe( + newKeepers, + newKeepers, + f, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + registry, + 'IncorrectNumberOfSigners', + ) + }) + + it('reverts on repeated signers', async () => { + const newSigners = [ + await personas.Eddy.getAddress(), + await personas.Eddy.getAddress(), + await personas.Eddy.getAddress(), + await personas.Eddy.getAddress(), + ] + await evmRevertCustomError( + registry + .connect(owner) + .setConfigTypeSafe( + newSigners, + newKeepers, + f, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + registry, + 'RepeatedSigner', + ) + }) + + it('reverts on repeated transmitters', async () => { + const newTransmitters = [ + await personas.Eddy.getAddress(), + await personas.Eddy.getAddress(), + await personas.Eddy.getAddress(), + await personas.Eddy.getAddress(), + ] + await evmRevertCustomError( + registry + .connect(owner) + .setConfigTypeSafe( + newKeepers, + newTransmitters, + f, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ), + registry, + 'RepeatedTransmitter', + ) + }) + + itMaybe('stores new config and emits event', async () => { + // Perform an upkeep so that totalPremium is updated + await registry.connect(admin).addFunds(upkeepId, toWei('100')) + let tx = await getTransmitTx(registry, keeper1, [upkeepId]) + await tx.wait() + + const newOffChainVersion = BigNumber.from('2') + const newOffChainConfig = '0x1122' + + const old = await registry.getState() + const oldState = old.state + assert(oldState.totalPremium.gt(BigNumber.from('0'))) + + const newSigners = newKeepers + tx = await registry + .connect(owner) + .setConfigTypeSafe( + newSigners, + newKeepers, + f, + config, + newOffChainVersion, + newOffChainConfig, + [], + [], + ) + + const updated = await registry.getState() + const updatedState = updated.state + assert(oldState.totalPremium.eq(updatedState.totalPremium)) + + // Old signer addresses which are not in new signers should be non active + for (let i = 0; i < signerAddresses.length; i++) { + const signer = signerAddresses[i] + if (!newSigners.includes(signer)) { + assert(!(await registry.getSignerInfo(signer)).active) + assert((await registry.getSignerInfo(signer)).index == 0) + } + } + // New signer addresses should be active + for (let i = 0; i < newSigners.length; i++) { + const signer = newSigners[i] + assert((await registry.getSignerInfo(signer)).active) + assert((await registry.getSignerInfo(signer)).index == i) + } + // Old transmitter addresses which are not in new transmitter should be non active, update lastCollected but retain other info + for (let i = 0; i < keeperAddresses.length; i++) { + const transmitter = keeperAddresses[i] + if (!newKeepers.includes(transmitter)) { + assert(!(await registry.getTransmitterInfo(transmitter)).active) + assert((await registry.getTransmitterInfo(transmitter)).index == i) + assert( + (await registry.getTransmitterInfo(transmitter)).lastCollected.eq( + oldState.totalPremium.sub( + oldState.totalPremium.mod(keeperAddresses.length), + ), + ), + ) + } + } + // New transmitter addresses should be active + for (let i = 0; i < newKeepers.length; i++) { + const transmitter = newKeepers[i] + assert((await registry.getTransmitterInfo(transmitter)).active) + assert((await registry.getTransmitterInfo(transmitter)).index == i) + assert( + (await registry.getTransmitterInfo(transmitter)).lastCollected.eq( + oldState.totalPremium, + ), + ) + } + + // config digest should be updated + assert(oldState.configCount + 1 == updatedState.configCount) + assert( + oldState.latestConfigBlockNumber != + updatedState.latestConfigBlockNumber, + ) + assert(oldState.latestConfigDigest != updatedState.latestConfigDigest) + + //New config should be updated + assert.deepEqual(updated.signers, newKeepers) + assert.deepEqual(updated.transmitters, newKeepers) + + // Event should have been emitted + await expect(tx).to.emit(registry, 'ConfigSet') + }) + }) + + describe('#cancelUpkeep', () => { + describe('when called by the admin', async () => { + describeMaybe('when an upkeep has been performed', async () => { + beforeEach(async () => { + await linkToken.connect(owner).approve(registry.address, toWei('100')) + await registry.connect(owner).addFunds(upkeepId, toWei('100')) + await getTransmitTx(registry, keeper1, [upkeepId]) + }) + + it('deducts a cancellation fee from the upkeep and adds to reserve', async () => { + const newMinUpkeepSpend = toWei('10') + const financeAdminAddress = await financeAdmin.getAddress() + + await registry.connect(owner).setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + { + checkGasLimit, + stalenessSeconds, + gasCeilingMultiplier, + maxCheckDataSize, + maxPerformDataSize, + maxRevertDataSize, + maxPerformGas, + fallbackGasPrice, + fallbackLinkPrice, + fallbackNativePrice, + transcoder: transcoder.address, + registrars: [], + upkeepPrivilegeManager: upkeepManager, + chainModule: moduleBase.address, + reorgProtectionEnabled: true, + financeAdmin: financeAdminAddress, + }, + offchainVersion, + offchainBytes, + [linkToken.address], + [ + { + gasFeePPB: paymentPremiumPPB, + flatFeeMilliCents, + priceFeed: linkUSDFeed.address, + fallbackPrice: fallbackLinkPrice, + minSpend: newMinUpkeepSpend, + decimals: 18, + }, + ], + ) + + const payee1Before = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const upkeepBefore = (await registry.getUpkeep(upkeepId)).balance + const ownerBefore = await registry.linkAvailableForPayment() + + const amountSpent = toWei('100').sub(upkeepBefore) + const cancellationFee = newMinUpkeepSpend.sub(amountSpent) + + await registry.connect(admin).cancelUpkeep(upkeepId) + + const payee1After = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const upkeepAfter = (await registry.getUpkeep(upkeepId)).balance + const ownerAfter = await registry.linkAvailableForPayment() + + // post upkeep balance should be previous balance minus cancellation fee + assert.isTrue(upkeepBefore.sub(cancellationFee).eq(upkeepAfter)) + // payee balance should not change + assert.isTrue(payee1Before.eq(payee1After)) + // owner should receive the cancellation fee + assert.isTrue(ownerAfter.sub(ownerBefore).eq(cancellationFee)) + }) + + it('deducts up to balance as cancellation fee', async () => { + // Very high min spend, should deduct whole balance as cancellation fees + const newMinUpkeepSpend = toWei('1000') + const financeAdminAddress = await financeAdmin.getAddress() + + await registry.connect(owner).setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + { + checkGasLimit, + stalenessSeconds, + gasCeilingMultiplier, + maxCheckDataSize, + maxPerformDataSize, + maxRevertDataSize, + maxPerformGas, + fallbackGasPrice, + fallbackLinkPrice, + fallbackNativePrice, + transcoder: transcoder.address, + registrars: [], + upkeepPrivilegeManager: upkeepManager, + chainModule: moduleBase.address, + reorgProtectionEnabled: true, + financeAdmin: financeAdminAddress, + }, + offchainVersion, + offchainBytes, + [linkToken.address], + [ + { + gasFeePPB: paymentPremiumPPB, + flatFeeMilliCents, + priceFeed: linkUSDFeed.address, + fallbackPrice: fallbackLinkPrice, + minSpend: newMinUpkeepSpend, + decimals: 18, + }, + ], + ) + const payee1Before = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const upkeepBefore = (await registry.getUpkeep(upkeepId)).balance + const ownerBefore = await registry.linkAvailableForPayment() + + await registry.connect(admin).cancelUpkeep(upkeepId) + const payee1After = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const ownerAfter = await registry.linkAvailableForPayment() + const upkeepAfter = (await registry.getUpkeep(upkeepId)).balance + + // all upkeep balance is deducted for cancellation fee + assert.equal(upkeepAfter.toNumber(), 0) + // payee balance should not change + assert.isTrue(payee1After.eq(payee1Before)) + // all upkeep balance is transferred to the owner + assert.isTrue(ownerAfter.sub(ownerBefore).eq(upkeepBefore)) + }) + + it('does not deduct cancellation fee if more than minUpkeepSpendDollars is spent', async () => { + // Very low min spend, already spent in one perform upkeep + const newMinUpkeepSpend = BigNumber.from(420) + const financeAdminAddress = await financeAdmin.getAddress() + + await registry.connect(owner).setConfigTypeSafe( + signerAddresses, + keeperAddresses, + f, + { + checkGasLimit, + stalenessSeconds, + gasCeilingMultiplier, + maxCheckDataSize, + maxPerformDataSize, + maxRevertDataSize, + maxPerformGas, + fallbackGasPrice, + fallbackLinkPrice, + fallbackNativePrice, + transcoder: transcoder.address, + registrars: [], + upkeepPrivilegeManager: upkeepManager, + chainModule: moduleBase.address, + reorgProtectionEnabled: true, + financeAdmin: financeAdminAddress, + }, + offchainVersion, + offchainBytes, + [linkToken.address], + [ + { + gasFeePPB: paymentPremiumPPB, + flatFeeMilliCents, + priceFeed: linkUSDFeed.address, + fallbackPrice: fallbackLinkPrice, + minSpend: newMinUpkeepSpend, + decimals: 18, + }, + ], + ) + const payee1Before = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const upkeepBefore = (await registry.getUpkeep(upkeepId)).balance + const ownerBefore = await registry.linkAvailableForPayment() + + await registry.connect(admin).cancelUpkeep(upkeepId) + const payee1After = await linkToken.balanceOf( + await payee1.getAddress(), + ) + const ownerAfter = await registry.linkAvailableForPayment() + const upkeepAfter = (await registry.getUpkeep(upkeepId)).balance + + // upkeep does not pay cancellation fee after cancellation because minimum upkeep spent is met + assert.isTrue(upkeepBefore.eq(upkeepAfter)) + // owner balance does not change + assert.isTrue(ownerAfter.eq(ownerBefore)) + // payee balance does not change + assert.isTrue(payee1Before.eq(payee1After)) + }) + }) + }) + }) + + describe('#withdrawPayment', () => { + beforeEach(async () => { + await linkToken.connect(owner).approve(registry.address, toWei('100')) + await registry.connect(owner).addFunds(upkeepId, toWei('100')) + await getTransmitTx(registry, keeper1, [upkeepId]) + }) + + it('reverts if called by anyone but the payee', async () => { + await evmRevertCustomError( + registry + .connect(payee2) + .withdrawPayment( + await keeper1.getAddress(), + await nonkeeper.getAddress(), + ), + registry, + 'OnlyCallableByPayee', + ) + }) + + it('reverts if called with the 0 address', async () => { + await evmRevertCustomError( + registry + .connect(payee2) + .withdrawPayment(await keeper1.getAddress(), zeroAddress), + registry, + 'InvalidRecipient', + ) + }) + + it('updates the balances', async () => { + const to = await nonkeeper.getAddress() + const keeperBefore = await registry.getTransmitterInfo( + await keeper1.getAddress(), + ) + const registrationBefore = (await registry.getUpkeep(upkeepId)).balance + const toLinkBefore = await linkToken.balanceOf(to) + const registryLinkBefore = await linkToken.balanceOf(registry.address) + const registryPremiumBefore = (await registry.getState()).state + .totalPremium + const ownerBefore = await registry.linkAvailableForPayment() + + // Withdrawing for first time, last collected = 0 + assert.equal(keeperBefore.lastCollected.toString(), '0') + + //// Do the thing + await registry + .connect(payee1) + .withdrawPayment(await keeper1.getAddress(), to) + + const keeperAfter = await registry.getTransmitterInfo( + await keeper1.getAddress(), + ) + const registrationAfter = (await registry.getUpkeep(upkeepId)).balance + const toLinkAfter = await linkToken.balanceOf(to) + const registryLinkAfter = await linkToken.balanceOf(registry.address) + const registryPremiumAfter = (await registry.getState()).state + .totalPremium + const ownerAfter = await registry.linkAvailableForPayment() + + // registry total premium should not change + assert.isTrue(registryPremiumBefore.eq(registryPremiumAfter)) + + // Last collected should be updated to premium-change + assert.isTrue( + keeperAfter.lastCollected.eq( + registryPremiumBefore.sub( + registryPremiumBefore.mod(keeperAddresses.length), + ), + ), + ) + + // owner balance should remain unchanged + assert.isTrue(ownerAfter.eq(ownerBefore)) + + assert.isTrue(keeperAfter.balance.eq(BigNumber.from(0))) + assert.isTrue(registrationBefore.eq(registrationAfter)) + assert.isTrue(toLinkBefore.add(keeperBefore.balance).eq(toLinkAfter)) + assert.isTrue( + registryLinkBefore.sub(keeperBefore.balance).eq(registryLinkAfter), + ) + }) + + it('emits a log announcing the withdrawal', async () => { + const balance = ( + await registry.getTransmitterInfo(await keeper1.getAddress()) + ).balance + const tx = await registry + .connect(payee1) + .withdrawPayment( + await keeper1.getAddress(), + await nonkeeper.getAddress(), + ) + await expect(tx) + .to.emit(registry, 'PaymentWithdrawn') + .withArgs( + await keeper1.getAddress(), + balance, + await nonkeeper.getAddress(), + await payee1.getAddress(), + ) + }) + }) + + describe('#checkCallback', () => { + it('returns false with appropriate failure reason when target callback reverts', async () => { + await streamsLookupUpkeep.setShouldRevertCallback(true) + + const values: any[] = ['0x1234', '0xabcd'] + const res = await registry + .connect(zeroAddress) + .callStatic.checkCallback(streamsLookupUpkeepId, values, '0x') + + assert.isFalse(res.upkeepNeeded) + assert.equal(res.performData, '0x') + assert.equal( + res.upkeepFailureReason, + UpkeepFailureReason.CHECK_CALLBACK_REVERTED, + ) + assert.isTrue(res.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + }) + + it('returns false with appropriate failure reason when target callback returns big performData', async () => { + let longBytes = '0x' + for (let i = 0; i <= maxPerformDataSize.toNumber(); i++) { + longBytes += '11' + } + const values: any[] = [longBytes, longBytes] + const res = await registry + .connect(zeroAddress) + .callStatic.checkCallback(streamsLookupUpkeepId, values, '0x') + + assert.isFalse(res.upkeepNeeded) + assert.equal(res.performData, '0x') + assert.equal( + res.upkeepFailureReason, + UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT, + ) + assert.isTrue(res.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + }) + + it('returns false with appropriate failure reason when target callback returns false', async () => { + await streamsLookupUpkeep.setCallbackReturnBool(false) + const values: any[] = ['0x1234', '0xabcd'] + const res = await registry + .connect(zeroAddress) + .callStatic.checkCallback(streamsLookupUpkeepId, values, '0x') + + assert.isFalse(res.upkeepNeeded) + assert.equal(res.performData, '0x') + assert.equal( + res.upkeepFailureReason, + UpkeepFailureReason.UPKEEP_NOT_NEEDED, + ) + assert.isTrue(res.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + }) + + it('succeeds with upkeep needed', async () => { + const values: any[] = ['0x1234', '0xabcd'] + + const res = await registry + .connect(zeroAddress) + .callStatic.checkCallback(streamsLookupUpkeepId, values, '0x') + const expectedPerformData = ethers.utils.defaultAbiCoder.encode( + ['bytes[]', 'bytes'], + [values, '0x'], + ) + + assert.isTrue(res.upkeepNeeded) + assert.equal(res.performData, expectedPerformData) + assert.equal(res.upkeepFailureReason, UpkeepFailureReason.NONE) + assert.isTrue(res.gasUsed.gt(BigNumber.from('0'))) // Some gas should be used + }) + }) + + describe('transmitterPremiumSplit [ @skip-coverage ]', () => { + beforeEach(async () => { + await linkToken.connect(owner).approve(registry.address, toWei('100')) + await registry.connect(owner).addFunds(upkeepId, toWei('100')) + }) + + it('splits premium evenly across transmitters', async () => { + // Do a transmit from keeper1 + await getTransmitTx(registry, keeper1, [upkeepId]) + + const registryPremium = (await registry.getState()).state.totalPremium + assert.isTrue(registryPremium.gt(BigNumber.from(0))) + + const premiumPerTransmitter = registryPremium.div( + BigNumber.from(keeperAddresses.length), + ) + const k1Balance = ( + await registry.getTransmitterInfo(await keeper1.getAddress()) + ).balance + // transmitter should be reimbursed for gas and get the premium + assert.isTrue(k1Balance.gt(premiumPerTransmitter)) + const k1GasReimbursement = k1Balance.sub(premiumPerTransmitter) + + const k2Balance = ( + await registry.getTransmitterInfo(await keeper2.getAddress()) + ).balance + // non transmitter should get its share of premium + assert.isTrue(k2Balance.eq(premiumPerTransmitter)) + + // Now do a transmit from keeper 2 + await getTransmitTx(registry, keeper2, [upkeepId]) + const registryPremiumNew = (await registry.getState()).state.totalPremium + assert.isTrue(registryPremiumNew.gt(registryPremium)) + const premiumPerTransmitterNew = registryPremiumNew.div( + BigNumber.from(keeperAddresses.length), + ) + const additionalPremium = premiumPerTransmitterNew.sub( + premiumPerTransmitter, + ) + + const k1BalanceNew = ( + await registry.getTransmitterInfo(await keeper1.getAddress()) + ).balance + // k1 should get the new premium + assert.isTrue( + k1BalanceNew.eq(k1GasReimbursement.add(premiumPerTransmitterNew)), + ) + + const k2BalanceNew = ( + await registry.getTransmitterInfo(await keeper2.getAddress()) + ).balance + // k2 should get gas reimbursement in addition to new premium + assert.isTrue(k2BalanceNew.gt(k2Balance.add(additionalPremium))) + }) + + it('updates last collected upon payment withdrawn', async () => { + // Do a transmit from keeper1 + await getTransmitTx(registry, keeper1, [upkeepId]) + + const registryPremium = (await registry.getState()).state.totalPremium + const k1 = await registry.getTransmitterInfo(await keeper1.getAddress()) + const k2 = await registry.getTransmitterInfo(await keeper2.getAddress()) + + // Withdrawing for first time, last collected = 0 + assert.isTrue(k1.lastCollected.eq(BigNumber.from(0))) + assert.isTrue(k2.lastCollected.eq(BigNumber.from(0))) + + //// Do the thing + await registry + .connect(payee1) + .withdrawPayment( + await keeper1.getAddress(), + await nonkeeper.getAddress(), + ) + + const k1New = await registry.getTransmitterInfo( + await keeper1.getAddress(), + ) + const k2New = await registry.getTransmitterInfo( + await keeper2.getAddress(), + ) + + // transmitter info lastCollected should be updated for k1, not for k2 + assert.isTrue( + k1New.lastCollected.eq( + registryPremium.sub(registryPremium.mod(keeperAddresses.length)), + ), + ) + assert.isTrue(k2New.lastCollected.eq(BigNumber.from(0))) + }) + + // itMaybe( + it('maintains consistent balance information across all parties', async () => { + // throughout transmits, withdrawals, setConfigs total claim on balances should remain less than expected balance + // some spare change can get lost but it should be less than maxAllowedSpareChange + + let maxAllowedSpareChange = BigNumber.from('0') + await verifyConsistentAccounting(maxAllowedSpareChange) + + await getTransmitTx(registry, keeper1, [upkeepId]) + maxAllowedSpareChange = maxAllowedSpareChange.add(BigNumber.from('31')) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await registry + .connect(payee1) + .withdrawPayment( + await keeper1.getAddress(), + await nonkeeper.getAddress(), + ) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await registry + .connect(payee2) + .withdrawPayment( + await keeper2.getAddress(), + await nonkeeper.getAddress(), + ) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await getTransmitTx(registry, keeper1, [upkeepId]) + maxAllowedSpareChange = maxAllowedSpareChange.add(BigNumber.from('31')) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await registry.connect(owner).setConfigTypeSafe( + signerAddresses.slice(2, 15), // only use 2-14th index keepers + keeperAddresses.slice(2, 15), + f, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await getTransmitTx(registry, keeper3, [upkeepId], { + startingSignerIndex: 2, + }) + maxAllowedSpareChange = maxAllowedSpareChange.add(BigNumber.from('13')) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await registry + .connect(payee1) + .withdrawPayment( + await keeper1.getAddress(), + await nonkeeper.getAddress(), + ) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await registry + .connect(payee3) + .withdrawPayment( + await keeper3.getAddress(), + await nonkeeper.getAddress(), + ) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await registry.connect(owner).setConfigTypeSafe( + signerAddresses.slice(0, 4), // only use 0-3rd index keepers + keeperAddresses.slice(0, 4), + f, + config, + offchainVersion, + offchainBytes, + baseConfig[6], + baseConfig[7], + ) + await verifyConsistentAccounting(maxAllowedSpareChange) + await getTransmitTx(registry, keeper1, [upkeepId]) + maxAllowedSpareChange = maxAllowedSpareChange.add(BigNumber.from('4')) + await getTransmitTx(registry, keeper3, [upkeepId]) + maxAllowedSpareChange = maxAllowedSpareChange.add(BigNumber.from('4')) + + await verifyConsistentAccounting(maxAllowedSpareChange) + await registry + .connect(payee5) + .withdrawPayment( + await keeper5.getAddress(), + await nonkeeper.getAddress(), + ) + await verifyConsistentAccounting(maxAllowedSpareChange) + + await registry + .connect(payee1) + .withdrawPayment( + await keeper1.getAddress(), + await nonkeeper.getAddress(), + ) + await verifyConsistentAccounting(maxAllowedSpareChange) + }) + }) +}) diff --git a/contracts/test/v0.8/automation/helpers.ts b/contracts/test/v0.8/automation/helpers.ts index b2cdfb4efd9..99f2cef9b87 100644 --- a/contracts/test/v0.8/automation/helpers.ts +++ b/contracts/test/v0.8/automation/helpers.ts @@ -9,6 +9,7 @@ import { IAutomationRegistryMaster__factory as IAutomationRegistryMasterFactory import { assert } from 'chai' import { FunctionFragment } from '@ethersproject/abi' import { AutomationRegistryLogicC2_3__factory as AutomationRegistryLogicC2_3Factory } from '../../../typechain/factories/AutomationRegistryLogicC2_3__factory' +import { ZKSyncAutomationRegistryLogicC2_3__factory as ZKSyncAutomationRegistryLogicC2_3Factory } from '../../../typechain/factories/ZKSyncAutomationRegistryLogicC2_3__factory' import { IAutomationRegistryMaster2_3 as IAutomationRegistry2_3 } from '../../../typechain/IAutomationRegistryMaster2_3' import { IAutomationRegistryMaster2_3__factory as IAutomationRegistryMaster2_3Factory } from '../../../typechain/factories/IAutomationRegistryMaster2_3__factory' @@ -212,3 +213,51 @@ export const deployRegistry23 = async ( const master = await registryFactory.connect(from).deploy(logicA.address) return IAutomationRegistryMaster2_3Factory.connect(master.address, from) } + +export const deployZKSyncRegistry23 = async ( + from: Signer, + link: Parameters[0], + linkUSD: Parameters[1], + nativeUSD: Parameters[2], + fastgas: Parameters[3], + allowedReadOnlyAddress: Parameters< + AutomationRegistryLogicC2_3Factory['deploy'] + >[5], + payoutMode: Parameters[6], + wrappedNativeTokenAddress: Parameters< + ZKSyncAutomationRegistryLogicC2_3Factory['deploy'] + >[7], +): Promise => { + const logicCFactory = await ethers.getContractFactory( + 'ZKSyncAutomationRegistryLogicC2_3', + ) + const logicBFactory = await ethers.getContractFactory( + 'ZKSyncAutomationRegistryLogicB2_3', + ) + const logicAFactory = await ethers.getContractFactory( + 'ZKSyncAutomationRegistryLogicA2_3', + ) + const registryFactory = await ethers.getContractFactory( + 'ZKSyncAutomationRegistry2_3', + ) + const forwarderLogicFactory = await ethers.getContractFactory( + 'AutomationForwarderLogic', + ) + const forwarderLogic = await forwarderLogicFactory.connect(from).deploy() + const logicC = await logicCFactory + .connect(from) + .deploy( + link, + linkUSD, + nativeUSD, + fastgas, + forwarderLogic.address, + allowedReadOnlyAddress, + payoutMode, + wrappedNativeTokenAddress, + ) + const logicB = await logicBFactory.connect(from).deploy(logicC.address) + const logicA = await logicAFactory.connect(from).deploy(logicB.address) + const master = await registryFactory.connect(from).deploy(logicA.address) + return IAutomationRegistryMaster2_3Factory.connect(master.address, from) +} diff --git a/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go b/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go index 66c47f4741f..e0de0b801d9 100644 --- a/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go +++ b/core/capabilities/ccip/ccip_integration_tests/ccipreader/ccipreader_test.go @@ -3,6 +3,7 @@ package ccipreader import ( "context" "math/big" + "sort" "testing" "time" @@ -110,7 +111,7 @@ func TestCCIPReader_CommitReportsGTETimestamp(t *testing.T) { ) require.NoError(t, err) return len(reports) == numReports-1 - }, testutils.WaitTimeout(t), 50*time.Millisecond) + }, 10*time.Second, 50*time.Millisecond) assert.Len(t, reports[0].Report.MerkleRoots, 1) assert.Equal(t, chainS1, reports[0].Report.MerkleRoots[0].ChainSel) @@ -250,6 +251,10 @@ func TestCCIPReader_MsgsBetweenSeqNums(t *testing.T) { s.sb.Commit() + // Need to replay as sometimes the logs are not picked up by the log poller (?) + // Maybe another situation where chain reader doesn't register filters as expected. + require.NoError(t, s.lp.Replay(ctx, 1)) + var msgs []cciptypes.Message require.Eventually(t, func() bool { msgs, err = s.reader.MsgsBetweenSeqNums( @@ -262,6 +267,10 @@ func TestCCIPReader_MsgsBetweenSeqNums(t *testing.T) { }, 10*time.Second, 100*time.Millisecond) require.Len(t, msgs, 2) + // sort to ensure ascending order of sequence numbers. + sort.Slice(msgs, func(i, j int) bool { + return msgs[i].Header.SequenceNumber < msgs[j].Header.SequenceNumber + }) require.Equal(t, cciptypes.SeqNum(10), msgs[0].Header.SequenceNumber) require.Equal(t, cciptypes.SeqNum(15), msgs[1].Header.SequenceNumber) for _, msg := range msgs { diff --git a/core/capabilities/registry.go b/core/capabilities/registry.go index 4da51a27b6b..47285505805 100644 --- a/core/capabilities/registry.go +++ b/core/capabilities/registry.go @@ -8,6 +8,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink/v2/core/logger" + p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" "github.com/smartcontractkit/chainlink/v2/core/services/registrysyncer" ) @@ -199,3 +200,30 @@ func NewRegistry(lggr logger.Logger) *Registry { lggr: lggr.Named("CapabilitiesRegistry"), } } + +// TestMetadataRegistry is a test implementation of the metadataRegistry +// interface. It is used when ExternalCapabilitiesRegistry is not available. +type TestMetadataRegistry struct{} + +func (t *TestMetadataRegistry) LocalNode(ctx context.Context) (capabilities.Node, error) { + peerID := p2ptypes.PeerID{} + workflowDON := capabilities.DON{ + ID: 1, + ConfigVersion: 1, + Members: []p2ptypes.PeerID{ + peerID, + }, + F: 0, + IsPublic: false, + AcceptsWorkflows: true, + } + return capabilities.Node{ + PeerID: &peerID, + WorkflowDON: workflowDON, + CapabilityDONs: []capabilities.DON{}, + }, nil +} + +func (t *TestMetadataRegistry) ConfigForCapability(ctx context.Context, capabilityID string, donID uint32) (registrysyncer.CapabilityConfiguration, error) { + return registrysyncer.CapabilityConfiguration{}, nil +} diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go index 07ea6206241..35dd214b1f5 100644 --- a/core/chains/evm/config/chaintype/chaintype.go +++ b/core/chains/evm/config/chaintype/chaintype.go @@ -14,6 +14,7 @@ const ( ChainGnosis ChainType = "gnosis" ChainHedera ChainType = "hedera" ChainKroma ChainType = "kroma" + ChainMantle ChainType = "mantle" ChainMetis ChainType = "metis" ChainOptimismBedrock ChainType = "optimismBedrock" ChainScroll ChainType = "scroll" @@ -37,7 +38,7 @@ func (c ChainType) IsL2() bool { func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: + case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: return true } return false @@ -57,6 +58,8 @@ func ChainTypeFromSlug(slug string) ChainType { return ChainHedera case "kroma": return ChainKroma + case "mantle": + return ChainMantle case "metis": return ChainMetis case "optimismBedrock": @@ -129,6 +132,7 @@ var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Joi string(ChainGnosis), string(ChainHedera), string(ChainKroma), + string(ChainMantle), string(ChainMetis), string(ChainOptimismBedrock), string(ChainScroll), diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index f707fab6841..e1249fdb7e9 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -43,7 +43,7 @@ const ( PollPeriod = 6 * time.Second ) -var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync} +var supportedChainTypes = []chaintype.ChainType{chaintype.ChainArbitrum, chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainZkSync, chaintype.ChainMantle} func IsRollupWithL1Support(chainType chaintype.ChainType) bool { return slices.Contains(supportedChainTypes, chainType) @@ -56,7 +56,7 @@ func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chai var l1Oracle L1Oracle var err error switch chainType { - case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll: + case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainMantle: l1Oracle, err = NewOpStackL1GasOracle(lggr, ethClient, chainType) case chaintype.ChainArbitrum: l1Oracle, err = NewArbitrumL1GasOracle(lggr, ethClient) diff --git a/core/chains/evm/gas/rollups/l1_oracle_abi.go b/core/chains/evm/gas/rollups/l1_oracle_abi.go index fa5d9c85391..848957ce53a 100644 --- a/core/chains/evm/gas/rollups/l1_oracle_abi.go +++ b/core/chains/evm/gas/rollups/l1_oracle_abi.go @@ -19,3 +19,4 @@ const OPBaseFeeScalarAbiString = `[{"inputs":[],"name":"baseFeeScalar","outputs" const OPBlobBaseFeeAbiString = `[{"inputs":[],"name":"blobBaseFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` const OPBlobBaseFeeScalarAbiString = `[{"inputs":[],"name":"blobBaseFeeScalar","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"}]` const OPDecimalsAbiString = `[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}]` +const MantleTokenRatioAbiString = `[{"inputs":[],"name":"tokenRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 6805cd7095b..1b93f8fc3f9 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -50,6 +50,7 @@ type optimismL1Oracle struct { blobBaseFeeCalldata []byte blobBaseFeeScalarCalldata []byte decimalsCalldata []byte + tokenRatioCalldata []byte isEcotoneCalldata []byte isEcotoneMethodAbi abi.ABI isFjordCalldata []byte @@ -87,7 +88,11 @@ const ( // decimals is a hex encoded call to: // `function decimals() public pure returns (uint256);` decimalsMethod = "decimals" - // OPGasOracleAddress is the address of the precompiled contract that exists on Optimism and Base. + // tokenRatio fetches the tokenRatio used for Mantle's gas price calculation + // tokenRatio is a hex encoded call to: + // `function tokenRatio() public pure returns (uint256);` + tokenRatioMethod = "tokenRatio" + // OPGasOracleAddress is the address of the precompiled contract that exists on Optimism, Base and Mantle. OPGasOracleAddress = "0x420000000000000000000000000000000000000F" // KromaGasOracleAddress is the address of the precompiled contract that exists on Kroma. KromaGasOracleAddress = "0x4200000000000000000000000000000000000005" @@ -98,7 +103,7 @@ const ( func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (*optimismL1Oracle, error) { var precompileAddress string switch chainType { - case chaintype.ChainOptimismBedrock: + case chaintype.ChainOptimismBedrock, chaintype.ChainMantle: precompileAddress = OPGasOracleAddress case chaintype.ChainKroma: precompileAddress = KromaGasOracleAddress @@ -187,6 +192,16 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", decimalsMethod, chainType, err) } + // Encode calldata for tokenRatio method + tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(MantleTokenRatioAbiString)) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() method ABI for chain: %s; %w", tokenRatioMethod, chainType, err) + } + tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) + if err != nil { + return nil, fmt.Errorf("failed to parse GasPriceOracle %s() calldata for chain: %s; %w", tokenRatioMethod, chainType, err) + } + return &optimismL1Oracle{ client: ethClient, pollPeriod: PollPeriod, @@ -208,6 +223,7 @@ func newOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainTy blobBaseFeeCalldata: blobBaseFeeCalldata, blobBaseFeeScalarCalldata: blobBaseFeeScalarCalldata, decimalsCalldata: decimalsCalldata, + tokenRatioCalldata: tokenRatioCalldata, isEcotoneCalldata: isEcotoneCalldata, isEcotoneMethodAbi: isEcotoneMethodAbi, isFjordCalldata: isFjordCalldata, @@ -346,6 +362,10 @@ func (o *optimismL1Oracle) GetGasCost(ctx context.Context, tx *gethtypes.Transac } func (o *optimismL1Oracle) GetDAGasPrice(ctx context.Context) (*big.Int, error) { + if o.chainType == chaintype.ChainMantle { + return o.getMantleGasPrice(ctx) + } + err := o.checkForUpgrade(ctx) if err != nil { return nil, err @@ -443,6 +463,69 @@ func (o *optimismL1Oracle) getV1GasPrice(ctx context.Context) (*big.Int, error) return new(big.Int).SetBytes(b), nil } +// Returns the gas price for Mantle. The formula is the same as Optimism Bedrock (getV1GasPrice), but the tokenRatio parameter is multiplied +func (o *optimismL1Oracle) getMantleGasPrice(ctx context.Context) (*big.Int, error) { + // call oracle to get l1BaseFee and tokenRatio + rpcBatchCalls := []rpc.BatchElem{ + { + Method: "eth_call", + Args: []any{ + map[string]interface{}{ + "from": common.Address{}, + "to": o.l1OracleAddress, + "data": hexutil.Bytes(o.l1BaseFeeCalldata), + }, + "latest", + }, + Result: new(string), + }, + { + Method: "eth_call", + Args: []any{ + map[string]interface{}{ + "from": common.Address{}, + "to": o.l1OracleAddress, + "data": hexutil.Bytes(o.tokenRatioCalldata), + }, + "latest", + }, + Result: new(string), + }, + } + + err := o.client.BatchCallContext(ctx, rpcBatchCalls) + if err != nil { + return nil, fmt.Errorf("fetch gas price parameters batch call failed: %w", err) + } + if rpcBatchCalls[0].Error != nil { + return nil, fmt.Errorf("%s call failed in a batch: %w", l1BaseFeeMethod, err) + } + if rpcBatchCalls[1].Error != nil { + return nil, fmt.Errorf("%s call failed in a batch: %w", tokenRatioMethod, err) + } + + // Extract values from responses + l1BaseFeeResult := *(rpcBatchCalls[0].Result.(*string)) + tokenRatioResult := *(rpcBatchCalls[1].Result.(*string)) + + // Decode the responses into bytes + l1BaseFeeBytes, err := hexutil.Decode(l1BaseFeeResult) + if err != nil { + return nil, fmt.Errorf("failed to decode %s rpc result: %w", l1BaseFeeMethod, err) + } + tokenRatioBytes, err := hexutil.Decode(tokenRatioResult) + if err != nil { + return nil, fmt.Errorf("failed to decode %s rpc result: %w", tokenRatioMethod, err) + } + + // Convert bytes to big int for calculations + l1BaseFee := new(big.Int).SetBytes(l1BaseFeeBytes) + tokenRatio := new(big.Int).SetBytes(tokenRatioBytes) + + // multiply l1BaseFee and tokenRatio and return + return new(big.Int).Mul(l1BaseFee, tokenRatio), nil +} + // Returns the scaled gas price using baseFeeScalar, l1BaseFee, blobBaseFeeScalar, and blobBaseFee fields from the oracle // Confirmed the same calculation is used to determine gas price for both Ecotone and Fjord func (o *optimismL1Oracle) getEcotoneFjordGasPrice(ctx context.Context) (*big.Int, error) { diff --git a/core/chains/evm/gas/rollups/op_l1_oracle_test.go b/core/chains/evm/gas/rollups/op_l1_oracle_test.go index f5f009f1ea6..88bb96534d3 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle_test.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle_test.go @@ -111,6 +111,94 @@ func TestOPL1Oracle_ReadV1GasPrice(t *testing.T) { } } +func TestOPL1Oracle_ReadMantleGasPrice(t *testing.T) { + l1BaseFee := big.NewInt(100) + tokenRatio := big.NewInt(40) + oracleAddress := common.HexToAddress("0x1234").String() + + t.Parallel() + t.Run("correctly fetches gas price if chain is Mantle", func(t *testing.T) { + // Encode calldata for l1BaseFee method + l1BaseFeeMethodAbi, err := abi.JSON(strings.NewReader(L1BaseFeeAbiString)) + require.NoError(t, err) + l1BaseFeeCalldata, err := l1BaseFeeMethodAbi.Pack(l1BaseFeeMethod) + require.NoError(t, err) + + // Encode calldata for tokenRatio method + tokenRatioMethodAbi, err := abi.JSON(strings.NewReader(MantleTokenRatioAbiString)) + require.NoError(t, err) + tokenRatioCalldata, err := tokenRatioMethodAbi.Pack(tokenRatioMethod) + require.NoError(t, err) + + ethClient := mocks.NewL1OracleClient(t) + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { + rpcElements := args.Get(1).([]rpc.BatchElem) + require.Equal(t, 2, len(rpcElements)) + for _, rE := range rpcElements { + require.Equal(t, "eth_call", rE.Method) + require.Equal(t, oracleAddress, rE.Args[0].(map[string]interface{})["to"]) + require.Equal(t, "latest", rE.Args[1]) + } + require.Equal(t, hexutil.Bytes(l1BaseFeeCalldata), rpcElements[0].Args[0].(map[string]interface{})["data"]) + require.Equal(t, hexutil.Bytes(tokenRatioCalldata), rpcElements[1].Args[0].(map[string]interface{})["data"]) + + res1 := common.BigToHash(l1BaseFee).Hex() + res2 := common.BigToHash(tokenRatio).Hex() + + rpcElements[0].Result = &res1 + rpcElements[1].Result = &res2 + }).Return(nil).Once() + + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) + require.NoError(t, err) + + gasPrice, err := oracle.GetDAGasPrice(tests.Context(t)) + require.NoError(t, err) + + assert.Equal(t, new(big.Int).Mul(l1BaseFee, tokenRatio), gasPrice) + }) + + t.Run("fetching Mantle price but rpc returns bad data", func(t *testing.T) { + ethClient := mocks.NewL1OracleClient(t) + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { + rpcElements := args.Get(1).([]rpc.BatchElem) + var badData = "zzz" + rpcElements[0].Result = &badData + rpcElements[1].Result = &badData + }).Return(nil).Once() + + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) + require.NoError(t, err) + _, err = oracle.GetDAGasPrice(tests.Context(t)) + assert.Error(t, err) + }) + + t.Run("fetching Mantle price but rpc parent call errors", func(t *testing.T) { + ethClient := mocks.NewL1OracleClient(t) + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Return(fmt.Errorf("revert")).Once() + + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) + require.NoError(t, err) + _, err = oracle.GetDAGasPrice(tests.Context(t)) + assert.Error(t, err) + }) + + t.Run("fetching Mantle price but one of the sub rpc call errors", func(t *testing.T) { + ethClient := mocks.NewL1OracleClient(t) + ethClient.On("BatchCallContext", mock.Anything, mock.IsType([]rpc.BatchElem{})).Run(func(args mock.Arguments) { + rpcElements := args.Get(1).([]rpc.BatchElem) + res := common.BigToHash(l1BaseFee).Hex() + rpcElements[0].Result = &res + rpcElements[1].Error = fmt.Errorf("revert") + }).Return(nil).Once() + + oracle, err := newOpStackL1GasOracle(logger.Test(t), ethClient, chaintype.ChainMantle, oracleAddress) + require.NoError(t, err) + _, err = oracle.GetDAGasPrice(tests.Context(t)) + assert.Error(t, err) + }) +} + func setupUpgradeCheck(t *testing.T, oracleAddress string, isFjord, isEcotone bool) *mocks.L1OracleClient { trueHex := "0x0000000000000000000000000000000000000000000000000000000000000001" falseHex := "0x0000000000000000000000000000000000000000000000000000000000000000" diff --git a/core/gethwrappers/generated/chain_module_base/chain_module_base.go b/core/gethwrappers/generated/chain_module_base/chain_module_base.go index 7f8a0b4c493..19bc49298f5 100644 --- a/core/gethwrappers/generated/chain_module_base/chain_module_base.go +++ b/core/gethwrappers/generated/chain_module_base/chain_module_base.go @@ -29,7 +29,7 @@ var ( ) var ChainModuleBaseMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"n\",\"type\":\"uint256\"}],\"name\":\"blockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"getCurrentL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGasOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainModuleFixedOverhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainModulePerByteOverhead\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"getMaxL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"n\",\"type\":\"uint256\"}],\"name\":\"blockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"getCurrentL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"l1Fee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGasOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainModuleFixedOverhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainModulePerByteOverhead\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"getMaxL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"maxL1Fee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", Bin: "0x608060405234801561001057600080fd5b50610155806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80637810d12a116100505780637810d12a1461006c57806385df51fd14610099578063de9ee35e146100ac57600080fd5b8063125441401461006c57806357e871e714610093575b600080fd5b61008061007a3660046100ef565b50600090565b6040519081526020015b60405180910390f35b43610080565b6100806100a73660046100ef565b6100c2565b6040805161012c8152600060208201520161008a565b600043821015806100dd57506101006100db8343610108565b115b156100ea57506000919050565b504090565b60006020828403121561010157600080fd5b5035919050565b81810381811115610142577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b9291505056fea164736f6c6343000813000a", } diff --git a/core/gethwrappers/generated/i_chain_module/i_chain_module.go b/core/gethwrappers/generated/i_chain_module/i_chain_module.go index 947a18c3194..e432d912cc5 100644 --- a/core/gethwrappers/generated/i_chain_module/i_chain_module.go +++ b/core/gethwrappers/generated/i_chain_module/i_chain_module.go @@ -29,7 +29,7 @@ var ( ) var IChainModuleMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"blockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getCurrentL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGasOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainModuleFixedOverhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainModulePerByteOverhead\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getMaxL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"blockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getCurrentL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"l1Fee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGasOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainModuleFixedOverhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainModulePerByteOverhead\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getMaxL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"maxL1Fee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", } var IChainModuleABI = IChainModuleMetaData.ABI @@ -150,9 +150,9 @@ func (_IChainModule *IChainModuleTransactorRaw) Transact(opts *bind.TransactOpts return _IChainModule.Contract.contract.Transact(opts, method, params...) } -func (_IChainModule *IChainModuleCaller) BlockHash(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) { +func (_IChainModule *IChainModuleCaller) BlockHash(opts *bind.CallOpts, blockNumber *big.Int) ([32]byte, error) { var out []interface{} - err := _IChainModule.contract.Call(opts, &out, "blockHash", arg0) + err := _IChainModule.contract.Call(opts, &out, "blockHash", blockNumber) if err != nil { return *new([32]byte), err @@ -164,12 +164,12 @@ func (_IChainModule *IChainModuleCaller) BlockHash(opts *bind.CallOpts, arg0 *bi } -func (_IChainModule *IChainModuleSession) BlockHash(arg0 *big.Int) ([32]byte, error) { - return _IChainModule.Contract.BlockHash(&_IChainModule.CallOpts, arg0) +func (_IChainModule *IChainModuleSession) BlockHash(blockNumber *big.Int) ([32]byte, error) { + return _IChainModule.Contract.BlockHash(&_IChainModule.CallOpts, blockNumber) } -func (_IChainModule *IChainModuleCallerSession) BlockHash(arg0 *big.Int) ([32]byte, error) { - return _IChainModule.Contract.BlockHash(&_IChainModule.CallOpts, arg0) +func (_IChainModule *IChainModuleCallerSession) BlockHash(blockNumber *big.Int) ([32]byte, error) { + return _IChainModule.Contract.BlockHash(&_IChainModule.CallOpts, blockNumber) } func (_IChainModule *IChainModuleCaller) BlockNumber(opts *bind.CallOpts) (*big.Int, error) { @@ -278,7 +278,7 @@ func (_IChainModule *IChainModule) Address() common.Address { } type IChainModuleInterface interface { - BlockHash(opts *bind.CallOpts, arg0 *big.Int) ([32]byte, error) + BlockHash(opts *bind.CallOpts, blockNumber *big.Int) ([32]byte, error) BlockNumber(opts *bind.CallOpts) (*big.Int, error) diff --git a/core/gethwrappers/generated/optimism_module_v2/optimism_module_v2.go b/core/gethwrappers/generated/optimism_module_v2/optimism_module_v2.go new file mode 100644 index 00000000000..abad079caa3 --- /dev/null +++ b/core/gethwrappers/generated/optimism_module_v2/optimism_module_v2.go @@ -0,0 +1,840 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package optimism_module_v2 + +import ( + "errors" + "fmt" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated" +) + +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +var OptimismModuleV2MetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"InvalidL1FeeCoefficient\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"L1FeeCoefficientSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"n\",\"type\":\"uint256\"}],\"name\":\"blockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getCurrentL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGasOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainModuleFixedOverhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainModulePerByteOverhead\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getL1FeeCoefficient\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"coefficient\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getMaxL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"setL1FeeCalculation\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60806040526001805460ff60a01b1916601960a21b17905534801561002357600080fd5b50338060008161007a5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b03848116919091179091558116156100aa576100aa816100b2565b50505061015b565b336001600160a01b0382160361010a5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610071565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b61073f8061016a6000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c80638da5cb5b11610076578063de9ee35e1161005b578063de9ee35e1461016a578063f22559a014610180578063f2fde38b1461019357600080fd5b80638da5cb5b1461011f578063d10a944e1461014757600080fd5b80637810d12a116100a75780637810d12a146100ef57806379ba50971461010257806385df51fd1461010c57600080fd5b806312544140146100c357806357e871e7146100e9575b600080fd5b6100d66100d136600461060c565b6101a6565b6040519081526020015b60405180910390f35b436100d6565b6100d66100fd36600461060c565b6101b7565b61010a6101f6565b005b6100d661011a36600461060c565b6102f8565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100e0565b60015474010000000000000000000000000000000000000000900460ff166100d6565b60408051616d60815260006020820152016100e0565b61010a61018e366004610625565b610325565b61010a6101a136600461064f565b6103f0565b60006101b182610404565b92915050565b600060646101c483610404565b6001546101ec919074010000000000000000000000000000000000000000900460ff166106b4565b6101b191906106cb565b60015473ffffffffffffffffffffffffffffffffffffffff16331461027c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6000438210158061031357506101006103118343610706565b115b1561032057506000919050565b504090565b61032d610494565b60648160ff161115610370576040517f1a8a06a000000000000000000000000000000000000000000000000000000000815260ff82166004820152602401610273565b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000060ff8416908102919091179091556040519081527f29ec9e31de0d3fe0208a7ccb792bbc26a854f123146110daa3a77219cb74a5549060200160405180910390a150565b6103f8610494565b61040181610517565b50565b6040517ff1c7a58b0000000000000000000000000000000000000000000000000000000081526004810182905260009073420000000000000000000000000000000000000f9063f1c7a58b90602401602060405180830381865afa158015610470573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101b19190610719565b60005473ffffffffffffffffffffffffffffffffffffffff163314610515576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610273565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610596576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610273565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b60006020828403121561061e57600080fd5b5035919050565b60006020828403121561063757600080fd5b813560ff8116811461064857600080fd5b9392505050565b60006020828403121561066157600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461064857600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820281158282048414176101b1576101b1610685565b600082610701577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b818103818111156101b1576101b1610685565b60006020828403121561072b57600080fd5b505191905056fea164736f6c6343000813000a", +} + +var OptimismModuleV2ABI = OptimismModuleV2MetaData.ABI + +var OptimismModuleV2Bin = OptimismModuleV2MetaData.Bin + +func DeployOptimismModuleV2(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *OptimismModuleV2, error) { + parsed, err := OptimismModuleV2MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(OptimismModuleV2Bin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &OptimismModuleV2{address: address, abi: *parsed, OptimismModuleV2Caller: OptimismModuleV2Caller{contract: contract}, OptimismModuleV2Transactor: OptimismModuleV2Transactor{contract: contract}, OptimismModuleV2Filterer: OptimismModuleV2Filterer{contract: contract}}, nil +} + +type OptimismModuleV2 struct { + address common.Address + abi abi.ABI + OptimismModuleV2Caller + OptimismModuleV2Transactor + OptimismModuleV2Filterer +} + +type OptimismModuleV2Caller struct { + contract *bind.BoundContract +} + +type OptimismModuleV2Transactor struct { + contract *bind.BoundContract +} + +type OptimismModuleV2Filterer struct { + contract *bind.BoundContract +} + +type OptimismModuleV2Session struct { + Contract *OptimismModuleV2 + CallOpts bind.CallOpts + TransactOpts bind.TransactOpts +} + +type OptimismModuleV2CallerSession struct { + Contract *OptimismModuleV2Caller + CallOpts bind.CallOpts +} + +type OptimismModuleV2TransactorSession struct { + Contract *OptimismModuleV2Transactor + TransactOpts bind.TransactOpts +} + +type OptimismModuleV2Raw struct { + Contract *OptimismModuleV2 +} + +type OptimismModuleV2CallerRaw struct { + Contract *OptimismModuleV2Caller +} + +type OptimismModuleV2TransactorRaw struct { + Contract *OptimismModuleV2Transactor +} + +func NewOptimismModuleV2(address common.Address, backend bind.ContractBackend) (*OptimismModuleV2, error) { + abi, err := abi.JSON(strings.NewReader(OptimismModuleV2ABI)) + if err != nil { + return nil, err + } + contract, err := bindOptimismModuleV2(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &OptimismModuleV2{address: address, abi: abi, OptimismModuleV2Caller: OptimismModuleV2Caller{contract: contract}, OptimismModuleV2Transactor: OptimismModuleV2Transactor{contract: contract}, OptimismModuleV2Filterer: OptimismModuleV2Filterer{contract: contract}}, nil +} + +func NewOptimismModuleV2Caller(address common.Address, caller bind.ContractCaller) (*OptimismModuleV2Caller, error) { + contract, err := bindOptimismModuleV2(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &OptimismModuleV2Caller{contract: contract}, nil +} + +func NewOptimismModuleV2Transactor(address common.Address, transactor bind.ContractTransactor) (*OptimismModuleV2Transactor, error) { + contract, err := bindOptimismModuleV2(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &OptimismModuleV2Transactor{contract: contract}, nil +} + +func NewOptimismModuleV2Filterer(address common.Address, filterer bind.ContractFilterer) (*OptimismModuleV2Filterer, error) { + contract, err := bindOptimismModuleV2(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &OptimismModuleV2Filterer{contract: contract}, nil +} + +func bindOptimismModuleV2(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := OptimismModuleV2MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +func (_OptimismModuleV2 *OptimismModuleV2Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _OptimismModuleV2.Contract.OptimismModuleV2Caller.contract.Call(opts, result, method, params...) +} + +func (_OptimismModuleV2 *OptimismModuleV2Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _OptimismModuleV2.Contract.OptimismModuleV2Transactor.contract.Transfer(opts) +} + +func (_OptimismModuleV2 *OptimismModuleV2Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _OptimismModuleV2.Contract.OptimismModuleV2Transactor.contract.Transact(opts, method, params...) +} + +func (_OptimismModuleV2 *OptimismModuleV2CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _OptimismModuleV2.Contract.contract.Call(opts, result, method, params...) +} + +func (_OptimismModuleV2 *OptimismModuleV2TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _OptimismModuleV2.Contract.contract.Transfer(opts) +} + +func (_OptimismModuleV2 *OptimismModuleV2TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _OptimismModuleV2.Contract.contract.Transact(opts, method, params...) +} + +func (_OptimismModuleV2 *OptimismModuleV2Caller) BlockHash(opts *bind.CallOpts, n *big.Int) ([32]byte, error) { + var out []interface{} + err := _OptimismModuleV2.contract.Call(opts, &out, "blockHash", n) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) BlockHash(n *big.Int) ([32]byte, error) { + return _OptimismModuleV2.Contract.BlockHash(&_OptimismModuleV2.CallOpts, n) +} + +func (_OptimismModuleV2 *OptimismModuleV2CallerSession) BlockHash(n *big.Int) ([32]byte, error) { + return _OptimismModuleV2.Contract.BlockHash(&_OptimismModuleV2.CallOpts, n) +} + +func (_OptimismModuleV2 *OptimismModuleV2Caller) BlockNumber(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _OptimismModuleV2.contract.Call(opts, &out, "blockNumber") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) BlockNumber() (*big.Int, error) { + return _OptimismModuleV2.Contract.BlockNumber(&_OptimismModuleV2.CallOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2CallerSession) BlockNumber() (*big.Int, error) { + return _OptimismModuleV2.Contract.BlockNumber(&_OptimismModuleV2.CallOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2Caller) GetCurrentL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) { + var out []interface{} + err := _OptimismModuleV2.contract.Call(opts, &out, "getCurrentL1Fee", dataSize) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) GetCurrentL1Fee(dataSize *big.Int) (*big.Int, error) { + return _OptimismModuleV2.Contract.GetCurrentL1Fee(&_OptimismModuleV2.CallOpts, dataSize) +} + +func (_OptimismModuleV2 *OptimismModuleV2CallerSession) GetCurrentL1Fee(dataSize *big.Int) (*big.Int, error) { + return _OptimismModuleV2.Contract.GetCurrentL1Fee(&_OptimismModuleV2.CallOpts, dataSize) +} + +func (_OptimismModuleV2 *OptimismModuleV2Caller) GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, + + error) { + var out []interface{} + err := _OptimismModuleV2.contract.Call(opts, &out, "getGasOverhead") + + outstruct := new(GetGasOverhead) + if err != nil { + return *outstruct, err + } + + outstruct.ChainModuleFixedOverhead = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + outstruct.ChainModulePerByteOverhead = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + + return *outstruct, err + +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) GetGasOverhead() (GetGasOverhead, + + error) { + return _OptimismModuleV2.Contract.GetGasOverhead(&_OptimismModuleV2.CallOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2CallerSession) GetGasOverhead() (GetGasOverhead, + + error) { + return _OptimismModuleV2.Contract.GetGasOverhead(&_OptimismModuleV2.CallOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2Caller) GetL1FeeCoefficient(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _OptimismModuleV2.contract.Call(opts, &out, "getL1FeeCoefficient") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) GetL1FeeCoefficient() (*big.Int, error) { + return _OptimismModuleV2.Contract.GetL1FeeCoefficient(&_OptimismModuleV2.CallOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2CallerSession) GetL1FeeCoefficient() (*big.Int, error) { + return _OptimismModuleV2.Contract.GetL1FeeCoefficient(&_OptimismModuleV2.CallOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2Caller) GetMaxL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) { + var out []interface{} + err := _OptimismModuleV2.contract.Call(opts, &out, "getMaxL1Fee", dataSize) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) GetMaxL1Fee(dataSize *big.Int) (*big.Int, error) { + return _OptimismModuleV2.Contract.GetMaxL1Fee(&_OptimismModuleV2.CallOpts, dataSize) +} + +func (_OptimismModuleV2 *OptimismModuleV2CallerSession) GetMaxL1Fee(dataSize *big.Int) (*big.Int, error) { + return _OptimismModuleV2.Contract.GetMaxL1Fee(&_OptimismModuleV2.CallOpts, dataSize) +} + +func (_OptimismModuleV2 *OptimismModuleV2Caller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _OptimismModuleV2.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) Owner() (common.Address, error) { + return _OptimismModuleV2.Contract.Owner(&_OptimismModuleV2.CallOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2CallerSession) Owner() (common.Address, error) { + return _OptimismModuleV2.Contract.Owner(&_OptimismModuleV2.CallOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2Transactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _OptimismModuleV2.contract.Transact(opts, "acceptOwnership") +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) AcceptOwnership() (*types.Transaction, error) { + return _OptimismModuleV2.Contract.AcceptOwnership(&_OptimismModuleV2.TransactOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2TransactorSession) AcceptOwnership() (*types.Transaction, error) { + return _OptimismModuleV2.Contract.AcceptOwnership(&_OptimismModuleV2.TransactOpts) +} + +func (_OptimismModuleV2 *OptimismModuleV2Transactor) SetL1FeeCalculation(opts *bind.TransactOpts, coefficient uint8) (*types.Transaction, error) { + return _OptimismModuleV2.contract.Transact(opts, "setL1FeeCalculation", coefficient) +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) SetL1FeeCalculation(coefficient uint8) (*types.Transaction, error) { + return _OptimismModuleV2.Contract.SetL1FeeCalculation(&_OptimismModuleV2.TransactOpts, coefficient) +} + +func (_OptimismModuleV2 *OptimismModuleV2TransactorSession) SetL1FeeCalculation(coefficient uint8) (*types.Transaction, error) { + return _OptimismModuleV2.Contract.SetL1FeeCalculation(&_OptimismModuleV2.TransactOpts, coefficient) +} + +func (_OptimismModuleV2 *OptimismModuleV2Transactor) TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) { + return _OptimismModuleV2.contract.Transact(opts, "transferOwnership", to) +} + +func (_OptimismModuleV2 *OptimismModuleV2Session) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _OptimismModuleV2.Contract.TransferOwnership(&_OptimismModuleV2.TransactOpts, to) +} + +func (_OptimismModuleV2 *OptimismModuleV2TransactorSession) TransferOwnership(to common.Address) (*types.Transaction, error) { + return _OptimismModuleV2.Contract.TransferOwnership(&_OptimismModuleV2.TransactOpts, to) +} + +type OptimismModuleV2L1FeeCoefficientSetIterator struct { + Event *OptimismModuleV2L1FeeCoefficientSet + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *OptimismModuleV2L1FeeCoefficientSetIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(OptimismModuleV2L1FeeCoefficientSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(OptimismModuleV2L1FeeCoefficientSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *OptimismModuleV2L1FeeCoefficientSetIterator) Error() error { + return it.fail +} + +func (it *OptimismModuleV2L1FeeCoefficientSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type OptimismModuleV2L1FeeCoefficientSet struct { + Coefficient uint8 + Raw types.Log +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) FilterL1FeeCoefficientSet(opts *bind.FilterOpts) (*OptimismModuleV2L1FeeCoefficientSetIterator, error) { + + logs, sub, err := _OptimismModuleV2.contract.FilterLogs(opts, "L1FeeCoefficientSet") + if err != nil { + return nil, err + } + return &OptimismModuleV2L1FeeCoefficientSetIterator{contract: _OptimismModuleV2.contract, event: "L1FeeCoefficientSet", logs: logs, sub: sub}, nil +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) WatchL1FeeCoefficientSet(opts *bind.WatchOpts, sink chan<- *OptimismModuleV2L1FeeCoefficientSet) (event.Subscription, error) { + + logs, sub, err := _OptimismModuleV2.contract.WatchLogs(opts, "L1FeeCoefficientSet") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(OptimismModuleV2L1FeeCoefficientSet) + if err := _OptimismModuleV2.contract.UnpackLog(event, "L1FeeCoefficientSet", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) ParseL1FeeCoefficientSet(log types.Log) (*OptimismModuleV2L1FeeCoefficientSet, error) { + event := new(OptimismModuleV2L1FeeCoefficientSet) + if err := _OptimismModuleV2.contract.UnpackLog(event, "L1FeeCoefficientSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type OptimismModuleV2OwnershipTransferRequestedIterator struct { + Event *OptimismModuleV2OwnershipTransferRequested + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *OptimismModuleV2OwnershipTransferRequestedIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(OptimismModuleV2OwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(OptimismModuleV2OwnershipTransferRequested) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *OptimismModuleV2OwnershipTransferRequestedIterator) Error() error { + return it.fail +} + +func (it *OptimismModuleV2OwnershipTransferRequestedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type OptimismModuleV2OwnershipTransferRequested struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*OptimismModuleV2OwnershipTransferRequestedIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _OptimismModuleV2.contract.FilterLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return &OptimismModuleV2OwnershipTransferRequestedIterator{contract: _OptimismModuleV2.contract, event: "OwnershipTransferRequested", logs: logs, sub: sub}, nil +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *OptimismModuleV2OwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _OptimismModuleV2.contract.WatchLogs(opts, "OwnershipTransferRequested", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(OptimismModuleV2OwnershipTransferRequested) + if err := _OptimismModuleV2.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) ParseOwnershipTransferRequested(log types.Log) (*OptimismModuleV2OwnershipTransferRequested, error) { + event := new(OptimismModuleV2OwnershipTransferRequested) + if err := _OptimismModuleV2.contract.UnpackLog(event, "OwnershipTransferRequested", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type OptimismModuleV2OwnershipTransferredIterator struct { + Event *OptimismModuleV2OwnershipTransferred + + contract *bind.BoundContract + event string + + logs chan types.Log + sub ethereum.Subscription + done bool + fail error +} + +func (it *OptimismModuleV2OwnershipTransferredIterator) Next() bool { + + if it.fail != nil { + return false + } + + if it.done { + select { + case log := <-it.logs: + it.Event = new(OptimismModuleV2OwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + + select { + case log := <-it.logs: + it.Event = new(OptimismModuleV2OwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +func (it *OptimismModuleV2OwnershipTransferredIterator) Error() error { + return it.fail +} + +func (it *OptimismModuleV2OwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +type OptimismModuleV2OwnershipTransferred struct { + From common.Address + To common.Address + Raw types.Log +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*OptimismModuleV2OwnershipTransferredIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _OptimismModuleV2.contract.FilterLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return &OptimismModuleV2OwnershipTransferredIterator{contract: _OptimismModuleV2.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *OptimismModuleV2OwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _OptimismModuleV2.contract.WatchLogs(opts, "OwnershipTransferred", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + + event := new(OptimismModuleV2OwnershipTransferred) + if err := _OptimismModuleV2.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +func (_OptimismModuleV2 *OptimismModuleV2Filterer) ParseOwnershipTransferred(log types.Log) (*OptimismModuleV2OwnershipTransferred, error) { + event := new(OptimismModuleV2OwnershipTransferred) + if err := _OptimismModuleV2.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +type GetGasOverhead struct { + ChainModuleFixedOverhead *big.Int + ChainModulePerByteOverhead *big.Int +} + +func (_OptimismModuleV2 *OptimismModuleV2) ParseLog(log types.Log) (generated.AbigenLog, error) { + switch log.Topics[0] { + case _OptimismModuleV2.abi.Events["L1FeeCoefficientSet"].ID: + return _OptimismModuleV2.ParseL1FeeCoefficientSet(log) + case _OptimismModuleV2.abi.Events["OwnershipTransferRequested"].ID: + return _OptimismModuleV2.ParseOwnershipTransferRequested(log) + case _OptimismModuleV2.abi.Events["OwnershipTransferred"].ID: + return _OptimismModuleV2.ParseOwnershipTransferred(log) + + default: + return nil, fmt.Errorf("abigen wrapper received unknown log topic: %v", log.Topics[0]) + } +} + +func (OptimismModuleV2L1FeeCoefficientSet) Topic() common.Hash { + return common.HexToHash("0x29ec9e31de0d3fe0208a7ccb792bbc26a854f123146110daa3a77219cb74a554") +} + +func (OptimismModuleV2OwnershipTransferRequested) Topic() common.Hash { + return common.HexToHash("0xed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae1278") +} + +func (OptimismModuleV2OwnershipTransferred) Topic() common.Hash { + return common.HexToHash("0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0") +} + +func (_OptimismModuleV2 *OptimismModuleV2) Address() common.Address { + return _OptimismModuleV2.address +} + +type OptimismModuleV2Interface interface { + BlockHash(opts *bind.CallOpts, n *big.Int) ([32]byte, error) + + BlockNumber(opts *bind.CallOpts) (*big.Int, error) + + GetCurrentL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) + + GetGasOverhead(opts *bind.CallOpts) (GetGasOverhead, + + error) + + GetL1FeeCoefficient(opts *bind.CallOpts) (*big.Int, error) + + GetMaxL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) + + Owner(opts *bind.CallOpts) (common.Address, error) + + AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) + + SetL1FeeCalculation(opts *bind.TransactOpts, coefficient uint8) (*types.Transaction, error) + + TransferOwnership(opts *bind.TransactOpts, to common.Address) (*types.Transaction, error) + + FilterL1FeeCoefficientSet(opts *bind.FilterOpts) (*OptimismModuleV2L1FeeCoefficientSetIterator, error) + + WatchL1FeeCoefficientSet(opts *bind.WatchOpts, sink chan<- *OptimismModuleV2L1FeeCoefficientSet) (event.Subscription, error) + + ParseL1FeeCoefficientSet(log types.Log) (*OptimismModuleV2L1FeeCoefficientSet, error) + + FilterOwnershipTransferRequested(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*OptimismModuleV2OwnershipTransferRequestedIterator, error) + + WatchOwnershipTransferRequested(opts *bind.WatchOpts, sink chan<- *OptimismModuleV2OwnershipTransferRequested, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferRequested(log types.Log) (*OptimismModuleV2OwnershipTransferRequested, error) + + FilterOwnershipTransferred(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*OptimismModuleV2OwnershipTransferredIterator, error) + + WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *OptimismModuleV2OwnershipTransferred, from []common.Address, to []common.Address) (event.Subscription, error) + + ParseOwnershipTransferred(log types.Log) (*OptimismModuleV2OwnershipTransferred, error) + + ParseLog(log types.Log) (generated.AbigenLog, error) + + Address() common.Address +} diff --git a/core/gethwrappers/generated/scroll_module/scroll_module.go b/core/gethwrappers/generated/scroll_module/scroll_module.go index 0a580546d14..e8f48da0d04 100644 --- a/core/gethwrappers/generated/scroll_module/scroll_module.go +++ b/core/gethwrappers/generated/scroll_module/scroll_module.go @@ -31,8 +31,8 @@ var ( ) var ScrollModuleMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"InvalidL1FeeCoefficient\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"L1FeeCoefficientSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"n\",\"type\":\"uint256\"}],\"name\":\"blockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getCurrentL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGasOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainModuleFixedOverhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainModulePerByteOverhead\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getMaxL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"s_l1FeeCoefficient\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"setL1FeeCalculation\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x60806040526001805460ff60a01b1916601960a21b17905534801561002357600080fd5b50338060008161007a5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b03848116919091179091558116156100aa576100aa816100b2565b50505061015b565b336001600160a01b0382160361010a5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610071565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b61093c8061016a6000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c80638da5cb5b11610076578063de9ee35e1161005b578063de9ee35e1461017e578063f22559a014610194578063f2fde38b146101a757600080fd5b80638da5cb5b1461011f57806390bd5c741461014757600080fd5b80637810d12a116100a75780637810d12a146100ef57806379ba50971461010257806385df51fd1461010c57600080fd5b806312544140146100c357806357e871e7146100e9575b600080fd5b6100d66100d13660046106b1565b6101ba565b6040519081526020015b60405180910390f35b436100d6565b6100d66100fd3660046106b1565b6101cb565b61010a61020a565b005b6100d661011a3660046106b1565b61030c565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100e0565b60015461016c9074010000000000000000000000000000000000000000900460ff1681565b60405160ff90911681526020016100e0565b6040805161afc8815260aa6020820152016100e0565b61010a6101a23660046106ca565b610339565b61010a6101b53660046106ed565b610404565b60006101c582610418565b92915050565b600060646101d883610418565b600154610200919074010000000000000000000000000000000000000000900460ff16610752565b6101c59190610769565b60015473ffffffffffffffffffffffffffffffffffffffff163314610290576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b60004382101580610327575061010061032583436107a4565b115b1561033457506000919050565b504090565b610341610539565b60648160ff161115610384576040517f1a8a06a000000000000000000000000000000000000000000000000000000000815260ff82166004820152602401610287565b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000060ff8416908102919091179091556040519081527f29ec9e31de0d3fe0208a7ccb792bbc26a854f123146110daa3a77219cb74a5549060200160405180910390a150565b61040c610539565b610415816105bc565b50565b600080610426836004610752565b67ffffffffffffffff81111561043e5761043e6107b7565b6040519080825280601f01601f191660200182016040528015610468576020820181803683370190505b50905073530000000000000000000000000000000000000273ffffffffffffffffffffffffffffffffffffffff166349948e0e826040518060c00160405280608c81526020016108a4608c91396040516020016104c692919061080a565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016104f19190610839565b602060405180830381865afa15801561050e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610532919061088a565b9392505050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146105ba576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610287565b565b3373ffffffffffffffffffffffffffffffffffffffff82160361063b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610287565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000602082840312156106c357600080fd5b5035919050565b6000602082840312156106dc57600080fd5b813560ff8116811461053257600080fd5b6000602082840312156106ff57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461053257600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820281158282048414176101c5576101c5610723565b60008261079f577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b818103818111156101c5576101c5610723565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60005b838110156108015781810151838201526020016107e9565b50506000910152565b6000835161081c8184602088016107e6565b8351908301906108308183602088016107e6565b01949350505050565b60208152600082518060208401526108588160408501602087016107e6565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b60006020828403121561089c57600080fd5b505191905056feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa164736f6c6343000813000a", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"InvalidL1FeeCoefficient\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"L1FeeCoefficientSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"n\",\"type\":\"uint256\"}],\"name\":\"blockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getCurrentL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getGasOverhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainModuleFixedOverhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"chainModulePerByteOverhead\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getL1FeeCoefficient\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"coefficient\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"dataSize\",\"type\":\"uint256\"}],\"name\":\"getMaxL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"coefficient\",\"type\":\"uint8\"}],\"name\":\"setL1FeeCalculation\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60806040526001805460ff60a01b1916601960a21b17905534801561002357600080fd5b50338060008161007a5760405162461bcd60e51b815260206004820152601860248201527f43616e6e6f7420736574206f776e657220746f207a65726f000000000000000060448201526064015b60405180910390fd5b600080546001600160a01b0319166001600160a01b03848116919091179091558116156100aa576100aa816100b2565b50505061015b565b336001600160a01b0382160361010a5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610071565b600180546001600160a01b0319166001600160a01b0383811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6109288061016a6000396000f3fe608060405234801561001057600080fd5b50600436106100be5760003560e01c80638da5cb5b11610076578063de9ee35e1161005b578063de9ee35e1461016a578063f22559a014610180578063f2fde38b1461019357600080fd5b80638da5cb5b1461011f578063d10a944e1461014757600080fd5b80637810d12a116100a75780637810d12a146100ef57806379ba50971461010257806385df51fd1461010c57600080fd5b806312544140146100c357806357e871e7146100e9575b600080fd5b6100d66100d136600461069d565b6101a6565b6040519081526020015b60405180910390f35b436100d6565b6100d66100fd36600461069d565b6101b7565b61010a6101f6565b005b6100d661011a36600461069d565b6102f8565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100e0565b60015474010000000000000000000000000000000000000000900460ff166100d6565b6040805161afc8815260aa6020820152016100e0565b61010a61018e3660046106b6565b610325565b61010a6101a13660046106d9565b6103f0565b60006101b182610404565b92915050565b600060646101c483610404565b6001546101ec919074010000000000000000000000000000000000000000900460ff1661073e565b6101b19190610755565b60015473ffffffffffffffffffffffffffffffffffffffff16331461027c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e65720000000000000000000060448201526064015b60405180910390fd5b60008054337fffffffffffffffffffffffff00000000000000000000000000000000000000008083168217845560018054909116905560405173ffffffffffffffffffffffffffffffffffffffff90921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6000438210158061031357506101006103118343610790565b115b1561032057506000919050565b504090565b61032d610525565b60648160ff161115610370576040517f1a8a06a000000000000000000000000000000000000000000000000000000000815260ff82166004820152602401610273565b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff167401000000000000000000000000000000000000000060ff8416908102919091179091556040519081527f29ec9e31de0d3fe0208a7ccb792bbc26a854f123146110daa3a77219cb74a5549060200160405180910390a150565b6103f8610525565b610401816105a8565b50565b60008061041283600461073e565b67ffffffffffffffff81111561042a5761042a6107a3565b6040519080825280601f01601f191660200182016040528015610454576020820181803683370190505b50905073530000000000000000000000000000000000000273ffffffffffffffffffffffffffffffffffffffff166349948e0e826040518060c00160405280608c8152602001610890608c91396040516020016104b29291906107f6565b6040516020818303038152906040526040518263ffffffff1660e01b81526004016104dd9190610825565b602060405180830381865afa1580156104fa573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061051e9190610876565b9392505050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146105a6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152606401610273565b565b3373ffffffffffffffffffffffffffffffffffffffff821603610627576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610273565b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691821790925560008054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6000602082840312156106af57600080fd5b5035919050565b6000602082840312156106c857600080fd5b813560ff8116811461051e57600080fd5b6000602082840312156106eb57600080fd5b813573ffffffffffffffffffffffffffffffffffffffff8116811461051e57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b80820281158282048414176101b1576101b161070f565b60008261078b577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b818103818111156101b1576101b161070f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60005b838110156107ed5781810151838201526020016107d5565b50506000910152565b600083516108088184602088016107d2565b83519083019061081c8183602088016107d2565b01949350505050565b60208152600082518060208401526108448160408501602087016107d2565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b60006020828403121561088857600080fd5b505191905056feffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa164736f6c6343000813000a", } var ScrollModuleABI = ScrollModuleMetaData.ABI @@ -267,9 +267,9 @@ func (_ScrollModule *ScrollModuleCallerSession) GetGasOverhead() (GetGasOverhead return _ScrollModule.Contract.GetGasOverhead(&_ScrollModule.CallOpts) } -func (_ScrollModule *ScrollModuleCaller) GetMaxL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) { +func (_ScrollModule *ScrollModuleCaller) GetL1FeeCoefficient(opts *bind.CallOpts) (*big.Int, error) { var out []interface{} - err := _ScrollModule.contract.Call(opts, &out, "getMaxL1Fee", dataSize) + err := _ScrollModule.contract.Call(opts, &out, "getL1FeeCoefficient") if err != nil { return *new(*big.Int), err @@ -281,56 +281,56 @@ func (_ScrollModule *ScrollModuleCaller) GetMaxL1Fee(opts *bind.CallOpts, dataSi } -func (_ScrollModule *ScrollModuleSession) GetMaxL1Fee(dataSize *big.Int) (*big.Int, error) { - return _ScrollModule.Contract.GetMaxL1Fee(&_ScrollModule.CallOpts, dataSize) +func (_ScrollModule *ScrollModuleSession) GetL1FeeCoefficient() (*big.Int, error) { + return _ScrollModule.Contract.GetL1FeeCoefficient(&_ScrollModule.CallOpts) } -func (_ScrollModule *ScrollModuleCallerSession) GetMaxL1Fee(dataSize *big.Int) (*big.Int, error) { - return _ScrollModule.Contract.GetMaxL1Fee(&_ScrollModule.CallOpts, dataSize) +func (_ScrollModule *ScrollModuleCallerSession) GetL1FeeCoefficient() (*big.Int, error) { + return _ScrollModule.Contract.GetL1FeeCoefficient(&_ScrollModule.CallOpts) } -func (_ScrollModule *ScrollModuleCaller) Owner(opts *bind.CallOpts) (common.Address, error) { +func (_ScrollModule *ScrollModuleCaller) GetMaxL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) { var out []interface{} - err := _ScrollModule.contract.Call(opts, &out, "owner") + err := _ScrollModule.contract.Call(opts, &out, "getMaxL1Fee", dataSize) if err != nil { - return *new(common.Address), err + return *new(*big.Int), err } - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) return out0, err } -func (_ScrollModule *ScrollModuleSession) Owner() (common.Address, error) { - return _ScrollModule.Contract.Owner(&_ScrollModule.CallOpts) +func (_ScrollModule *ScrollModuleSession) GetMaxL1Fee(dataSize *big.Int) (*big.Int, error) { + return _ScrollModule.Contract.GetMaxL1Fee(&_ScrollModule.CallOpts, dataSize) } -func (_ScrollModule *ScrollModuleCallerSession) Owner() (common.Address, error) { - return _ScrollModule.Contract.Owner(&_ScrollModule.CallOpts) +func (_ScrollModule *ScrollModuleCallerSession) GetMaxL1Fee(dataSize *big.Int) (*big.Int, error) { + return _ScrollModule.Contract.GetMaxL1Fee(&_ScrollModule.CallOpts, dataSize) } -func (_ScrollModule *ScrollModuleCaller) SL1FeeCoefficient(opts *bind.CallOpts) (uint8, error) { +func (_ScrollModule *ScrollModuleCaller) Owner(opts *bind.CallOpts) (common.Address, error) { var out []interface{} - err := _ScrollModule.contract.Call(opts, &out, "s_l1FeeCoefficient") + err := _ScrollModule.contract.Call(opts, &out, "owner") if err != nil { - return *new(uint8), err + return *new(common.Address), err } - out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) return out0, err } -func (_ScrollModule *ScrollModuleSession) SL1FeeCoefficient() (uint8, error) { - return _ScrollModule.Contract.SL1FeeCoefficient(&_ScrollModule.CallOpts) +func (_ScrollModule *ScrollModuleSession) Owner() (common.Address, error) { + return _ScrollModule.Contract.Owner(&_ScrollModule.CallOpts) } -func (_ScrollModule *ScrollModuleCallerSession) SL1FeeCoefficient() (uint8, error) { - return _ScrollModule.Contract.SL1FeeCoefficient(&_ScrollModule.CallOpts) +func (_ScrollModule *ScrollModuleCallerSession) Owner() (common.Address, error) { + return _ScrollModule.Contract.Owner(&_ScrollModule.CallOpts) } func (_ScrollModule *ScrollModuleTransactor) AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { @@ -804,12 +804,12 @@ type ScrollModuleInterface interface { error) + GetL1FeeCoefficient(opts *bind.CallOpts) (*big.Int, error) + GetMaxL1Fee(opts *bind.CallOpts, dataSize *big.Int) (*big.Int, error) Owner(opts *bind.CallOpts) (common.Address, error) - SL1FeeCoefficient(opts *bind.CallOpts) (uint8, error) - AcceptOwnership(opts *bind.TransactOpts) (*types.Transaction, error) SetL1FeeCalculation(opts *bind.TransactOpts, coefficient uint8) (*types.Transaction, error) diff --git a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index ee2ad5867cd..8862fef8c8c 100644 --- a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -23,7 +23,7 @@ batch_blockhash_store: ../../contracts/solc/v0.8.19/BatchBlockhashStore/BatchBlo batch_vrf_coordinator_v2: ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2/BatchVRFCoordinatorV2.abi ../../contracts/solc/v0.8.6/BatchVRFCoordinatorV2/BatchVRFCoordinatorV2.bin 4512f4313bc5c078215c9241a69045a2a3cfecd6adfcef2f13037183a2d71483 batch_vrf_coordinator_v2plus: ../../contracts/solc/v0.8.19/BatchVRFCoordinatorV2Plus/BatchVRFCoordinatorV2Plus.abi ../../contracts/solc/v0.8.19/BatchVRFCoordinatorV2Plus/BatchVRFCoordinatorV2Plus.bin f13715b38b5b9084b08bffa571fb1c8ef686001535902e1255052f074b31ad4e blockhash_store: ../../contracts/solc/v0.8.19/BlockhashStore/BlockhashStore.abi ../../contracts/solc/v0.8.19/BlockhashStore/BlockhashStore.bin 31b118f9577240c8834c35f8b5a1440e82a6ca8aea702970de2601824b6ab0e1 -chain_module_base: ../../contracts/solc/v0.8.19/ChainModuleBase/ChainModuleBase.abi ../../contracts/solc/v0.8.19/ChainModuleBase/ChainModuleBase.bin c318d55aae33f370881fec220987a401a06ef7a0da834cea90a68749f85ac96f +chain_module_base: ../../contracts/solc/v0.8.19/ChainModuleBase/ChainModuleBase.abi ../../contracts/solc/v0.8.19/ChainModuleBase/ChainModuleBase.bin 7a82cc28014761090185c2650239ad01a0901181f1b2b899b42ca293bcda3741 chain_reader_tester: ../../contracts/solc/v0.8.19/ChainReaderTester/ChainReaderTester.abi ../../contracts/solc/v0.8.19/ChainReaderTester/ChainReaderTester.bin b207f9e6bf71e445a2664a602677011b87b80bf95c6352fd7869f1a9ddb08a5b chain_specific_util_helper: ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper/ChainSpecificUtilHelper.abi ../../contracts/solc/v0.8.6/ChainSpecificUtilHelper/ChainSpecificUtilHelper.bin 66eb30b0717fefe05672df5ec863c0b9a5a654623c4757307a2726d8f31e26b1 counter: ../../contracts/solc/v0.8.6/Counter/Counter.abi ../../contracts/solc/v0.8.6/Counter/Counter.bin 6ca06e000e8423573ffa0bdfda749d88236ab3da2a4cbb4a868c706da90488c9 @@ -35,7 +35,7 @@ gas_wrapper_mock: ../../contracts/solc/v0.8.6/KeeperRegistryCheckUpkeepGasUsageW i_automation_registry_master_wrapper_2_2: ../../contracts/solc/v0.8.19/IAutomationRegistryMaster/IAutomationRegistryMaster.abi ../../contracts/solc/v0.8.19/IAutomationRegistryMaster/IAutomationRegistryMaster.bin 9ff7087179f89f9b05964ebc3e71332fce11f1b8e85058f7b16b3bc0dd6fb96b i_automation_registry_master_wrapper_2_3: ../../contracts/solc/v0.8.19/IAutomationRegistryMaster2_3/IAutomationRegistryMaster2_3.abi ../../contracts/solc/v0.8.19/IAutomationRegistryMaster2_3/IAutomationRegistryMaster2_3.bin 06cc87c122452f63fbe84f65329978f30281613be0caa261e53503d94763e921 i_automation_v21_plus_common: ../../contracts/solc/v0.8.19/IAutomationV21PlusCommon/IAutomationV21PlusCommon.abi ../../contracts/solc/v0.8.19/IAutomationV21PlusCommon/IAutomationV21PlusCommon.bin e8a601ec382c0a2e83c49759de13b0622b5e04e6b95901e96a1e9504329e594c -i_chain_module: ../../contracts/solc/v0.8.19/IChainModule/IChainModule.abi ../../contracts/solc/v0.8.19/IChainModule/IChainModule.bin 17ed358d5635be8959c7dcdccc08d40b1fb1aa40a4d7624455dfb725d79ca7d9 +i_chain_module: ../../contracts/solc/v0.8.19/IChainModule/IChainModule.abi ../../contracts/solc/v0.8.19/IChainModule/IChainModule.bin 8ccb8fcfd1ae331a46b4469e1567c380e2a6d2bf21a9976d6c4c655a716aaa42 i_keeper_registry_master_wrapper_2_1: ../../contracts/solc/v0.8.16/IKeeperRegistryMaster/IKeeperRegistryMaster.abi ../../contracts/solc/v0.8.16/IKeeperRegistryMaster/IKeeperRegistryMaster.bin ee0f150b3afbab2df3d24ff3f4c87851efa635da30db04cd1f70cb4e185a1781 i_log_automation: ../../contracts/solc/v0.8.16/ILogAutomation/ILogAutomation.abi ../../contracts/solc/v0.8.16/ILogAutomation/ILogAutomation.bin 296beccb6af655d6fc3a6e676b244831cce2da6688d3afc4f21f8738ae59e03e keeper_consumer_performance_wrapper: ../../contracts/solc/v0.8.16/KeeperConsumerPerformance/KeeperConsumerPerformance.abi ../../contracts/solc/v0.8.16/KeeperConsumerPerformance/KeeperConsumerPerformance.bin eeda39f5d3e1c8ffa0fb6cd1803731b98a4bc262d41833458e3fe8b40933ae90 @@ -60,9 +60,9 @@ mock_ethusd_aggregator_wrapper: ../../contracts/solc/v0.8.19/MockETHUSDAggregato offchain_aggregator_wrapper: OffchainAggregator/OffchainAggregator.abi - 5c8d6562e94166d4790f1ee6e4321d359d9f7262e6c5452a712b1f1c896f45cf operator_factory: ../../contracts/solc/v0.8.19/OperatorFactory/OperatorFactory.abi ../../contracts/solc/v0.8.19/OperatorFactory/OperatorFactory.bin 88e6baa5d9b255eea02616fbcb2cbe21a25ab46adeb6395f6289d169dec949ae operator_wrapper: ../../contracts/solc/v0.8.19/Operator/Operator.abi ../../contracts/solc/v0.8.19/Operator/Operator.bin 23c3888eaa7259e6adf2153d09abae8f4b1987dc44200363faab1e65483f32d5 -optimism_module: ../../contracts/solc/v0.8.19/OptimismModule/OptimismModule.abi ../../contracts/solc/v0.8.19/OptimismModule/OptimismModule.bin fb34a04d32c6f4ccc860962b740bfde91e5bdfd88cbac0711b41f25f6fd23a85 +optimism_module_v2: ../../contracts/solc/v0.8.19/OptimismModuleV2/OptimismModuleV2.abi ../../contracts/solc/v0.8.19/OptimismModuleV2/OptimismModuleV2.bin 6bc8f93d3a49b3fdecc169214565e6fe5690427860ca4f674818c611dd719502 perform_data_checker_wrapper: ../../contracts/solc/v0.8.16/PerformDataChecker/PerformDataChecker.abi ../../contracts/solc/v0.8.16/PerformDataChecker/PerformDataChecker.bin 48d8309c2117c29a24e1155917ab0b780956b2cd6a8a39ef06ae66a7f6d94f73 -scroll_module: ../../contracts/solc/v0.8.19/ScrollModule/ScrollModule.abi ../../contracts/solc/v0.8.19/ScrollModule/ScrollModule.bin ddd46b32aa5eb8dc252b10391f6c3345a4c80445c6c058257ceb16eccc074268 +scroll_module: ../../contracts/solc/v0.8.19/ScrollModule/ScrollModule.abi ../../contracts/solc/v0.8.19/ScrollModule/ScrollModule.bin 0b99b89ff0c8d95a2ab273c93355e572b9e052ce2a9507498a06e0915b541a86 simple_log_upkeep_counter_wrapper: ../../contracts/solc/v0.8.6/SimpleLogUpkeepCounter/SimpleLogUpkeepCounter.abi ../../contracts/solc/v0.8.6/SimpleLogUpkeepCounter/SimpleLogUpkeepCounter.bin 7557d117a066cd8cf35f635bc085ee11795442073c18f8610ede9037b74fd814 solidity_vrf_consumer_interface_v08: ../../contracts/solc/v0.8.6/VRFConsumer/VRFConsumer.abi ../../contracts/solc/v0.8.6/VRFConsumer/VRFConsumer.bin b14f9136b15e3dc9d6154d5700f3ed4cf88ddc4f70f20c3bb57fc46050904c8f solidity_vrf_request_id_v08: ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.abi ../../contracts/solc/v0.8.6/VRFRequestIDBaseTestHelper/VRFRequestIDBaseTestHelper.bin f2559015d6f3e5d285c57b011be9b2300632e93dd6c4524e58202d6200f09edc diff --git a/core/gethwrappers/go_generate.go b/core/gethwrappers/go_generate.go index 95485faf4bb..d877c7a8dbb 100644 --- a/core/gethwrappers/go_generate.go +++ b/core/gethwrappers/go_generate.go @@ -53,7 +53,7 @@ package gethwrappers //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/AutomationUtils2_3/AutomationUtils2_3.abi ../../contracts/solc/v0.8.19/AutomationUtils2_3/AutomationUtils2_3.bin AutomationUtils automation_utils_2_3 //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/ArbitrumModule/ArbitrumModule.abi ../../contracts/solc/v0.8.19/ArbitrumModule/ArbitrumModule.bin ArbitrumModule arbitrum_module //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/ChainModuleBase/ChainModuleBase.abi ../../contracts/solc/v0.8.19/ChainModuleBase/ChainModuleBase.bin ChainModuleBase chain_module_base -//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/OptimismModule/OptimismModule.abi ../../contracts/solc/v0.8.19/OptimismModule/OptimismModule.bin OptimismModule optimism_module +//go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/OptimismModuleV2/OptimismModuleV2.abi ../../contracts/solc/v0.8.19/OptimismModuleV2/OptimismModuleV2.bin OptimismModuleV2 optimism_module_v2 //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/ScrollModule/ScrollModule.abi ../../contracts/solc/v0.8.19/ScrollModule/ScrollModule.bin ScrollModule scroll_module //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/IChainModule/IChainModule.abi ../../contracts/solc/v0.8.19/IChainModule/IChainModule.bin IChainModule i_chain_module //go:generate go run ./generation/generate/wrap.go ../../contracts/solc/v0.8.19/IAutomationV21PlusCommon/IAutomationV21PlusCommon.abi ../../contracts/solc/v0.8.19/IAutomationV21PlusCommon/IAutomationV21PlusCommon.bin IAutomationV21PlusCommon i_automation_v21_plus_common diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 4045c3b5e8f..f891dc3e142 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -22,7 +22,7 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/spf13/cobra v1.8.0 @@ -274,7 +274,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect @@ -330,17 +330,17 @@ require ( go.uber.org/ratelimit v0.3.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/tools v0.24.0 // indirect gonum.org/v1/gonum v0.15.0 // indirect google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index bbaf5271d4f..d1c8c65a5ec 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1186,16 +1186,16 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95 h1:LAgJTg9Yr/uCo2g7Krp88Dco2U45Y6sbJVl8uKoLkys= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95/go.mod h1:/ZWraCBaDDgaIN1prixYcbVvIk/6HeED9+8zbWQ+TMo= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33 h1:bNsry/El6yigB00BBU3iIOhZHiPm5MgCrDkJEFNXL1U= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097 h1:88WOOrXy7t8IxS+91AKItN/HTzIHvEgFNetZDOhosKc= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097/go.mod h1:HqdnbHS9j9EKPidpSUI9S37zl0blbVjB+NP4ztdYta8= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f h1:I9fTBJpHkeldFplXUy71eLIn6A6GxuR4xrABoUeD+CM= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f/go.mod h1:V/86loaFSH0dqqUEHqyXVbyNqDRSjvcf9BRomWFTljU= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564 h1:8ZzsGNhqYxmQ/QMO1fuXO7u9Vpl9YUvPJK+td/ZaBJA= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564/go.mod h1:Ml88TJTwZCj6yHDkAEN/EhxVutzSlk+kDZgfibRIqF0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f h1:b0Ifwk7eK3fwJ0R69Ovhv5XvZ1/TUAfHkU5Jp7wbNZ0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f/go.mod h1:Ml88TJTwZCj6yHDkAEN/EhxVutzSlk+kDZgfibRIqF0= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799/go.mod h1:UVFRacRkP7O7TQAzFmR52v5mUlxf+G1ovMlCQAB/cHU= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 h1:TFe+FvzxClblt6qRfqEhUfa4kFQx5UobuoFGO2W4mMo= @@ -1474,8 +1474,8 @@ golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1486,8 +1486,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1515,8 +1515,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1573,8 +1573,8 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1599,8 +1599,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1686,8 +1686,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1695,8 +1695,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1709,8 +1709,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1782,8 +1782,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/core/services/ccip/mocks/orm.go b/core/services/ccip/mocks/orm.go index 8a987c21602..0c9086def7e 100644 --- a/core/services/ccip/mocks/orm.go +++ b/core/services/ccip/mocks/orm.go @@ -8,6 +8,8 @@ import ( ccip "github.com/smartcontractkit/chainlink/v2/core/services/ccip" mock "github.com/stretchr/testify/mock" + + time "time" ) // ORM is an autogenerated mock type for the ORM type @@ -23,102 +25,6 @@ func (_m *ORM) EXPECT() *ORM_Expecter { return &ORM_Expecter{mock: &_m.Mock} } -// ClearGasPricesByDestChain provides a mock function with given fields: ctx, destChainSelector, expireSec -func (_m *ORM) ClearGasPricesByDestChain(ctx context.Context, destChainSelector uint64, expireSec int) error { - ret := _m.Called(ctx, destChainSelector, expireSec) - - if len(ret) == 0 { - panic("no return value specified for ClearGasPricesByDestChain") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, int) error); ok { - r0 = rf(ctx, destChainSelector, expireSec) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ORM_ClearGasPricesByDestChain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearGasPricesByDestChain' -type ORM_ClearGasPricesByDestChain_Call struct { - *mock.Call -} - -// ClearGasPricesByDestChain is a helper method to define mock.On call -// - ctx context.Context -// - destChainSelector uint64 -// - expireSec int -func (_e *ORM_Expecter) ClearGasPricesByDestChain(ctx interface{}, destChainSelector interface{}, expireSec interface{}) *ORM_ClearGasPricesByDestChain_Call { - return &ORM_ClearGasPricesByDestChain_Call{Call: _e.mock.On("ClearGasPricesByDestChain", ctx, destChainSelector, expireSec)} -} - -func (_c *ORM_ClearGasPricesByDestChain_Call) Run(run func(ctx context.Context, destChainSelector uint64, expireSec int)) *ORM_ClearGasPricesByDestChain_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].(int)) - }) - return _c -} - -func (_c *ORM_ClearGasPricesByDestChain_Call) Return(_a0 error) *ORM_ClearGasPricesByDestChain_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ORM_ClearGasPricesByDestChain_Call) RunAndReturn(run func(context.Context, uint64, int) error) *ORM_ClearGasPricesByDestChain_Call { - _c.Call.Return(run) - return _c -} - -// ClearTokenPricesByDestChain provides a mock function with given fields: ctx, destChainSelector, expireSec -func (_m *ORM) ClearTokenPricesByDestChain(ctx context.Context, destChainSelector uint64, expireSec int) error { - ret := _m.Called(ctx, destChainSelector, expireSec) - - if len(ret) == 0 { - panic("no return value specified for ClearTokenPricesByDestChain") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, int) error); ok { - r0 = rf(ctx, destChainSelector, expireSec) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ORM_ClearTokenPricesByDestChain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearTokenPricesByDestChain' -type ORM_ClearTokenPricesByDestChain_Call struct { - *mock.Call -} - -// ClearTokenPricesByDestChain is a helper method to define mock.On call -// - ctx context.Context -// - destChainSelector uint64 -// - expireSec int -func (_e *ORM_Expecter) ClearTokenPricesByDestChain(ctx interface{}, destChainSelector interface{}, expireSec interface{}) *ORM_ClearTokenPricesByDestChain_Call { - return &ORM_ClearTokenPricesByDestChain_Call{Call: _e.mock.On("ClearTokenPricesByDestChain", ctx, destChainSelector, expireSec)} -} - -func (_c *ORM_ClearTokenPricesByDestChain_Call) Run(run func(ctx context.Context, destChainSelector uint64, expireSec int)) *ORM_ClearTokenPricesByDestChain_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].(int)) - }) - return _c -} - -func (_c *ORM_ClearTokenPricesByDestChain_Call) Return(_a0 error) *ORM_ClearTokenPricesByDestChain_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *ORM_ClearTokenPricesByDestChain_Call) RunAndReturn(run func(context.Context, uint64, int) error) *ORM_ClearTokenPricesByDestChain_Call { - _c.Call.Return(run) - return _c -} - // GetGasPricesByDestChain provides a mock function with given fields: ctx, destChainSelector func (_m *ORM) GetGasPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]ccip.GasPrice, error) { ret := _m.Called(ctx, destChainSelector) @@ -237,100 +143,119 @@ func (_c *ORM_GetTokenPricesByDestChain_Call) RunAndReturn(run func(context.Cont return _c } -// InsertGasPricesForDestChain provides a mock function with given fields: ctx, destChainSelector, jobId, gasPrices -func (_m *ORM) InsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, gasPrices []ccip.GasPriceUpdate) error { - ret := _m.Called(ctx, destChainSelector, jobId, gasPrices) +// UpsertGasPricesForDestChain provides a mock function with given fields: ctx, destChainSelector, gasPrices +func (_m *ORM) UpsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, gasPrices []ccip.GasPrice) (int64, error) { + ret := _m.Called(ctx, destChainSelector, gasPrices) if len(ret) == 0 { - panic("no return value specified for InsertGasPricesForDestChain") + panic("no return value specified for UpsertGasPricesForDestChain") } - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, int32, []ccip.GasPriceUpdate) error); ok { - r0 = rf(ctx, destChainSelector, jobId, gasPrices) + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []ccip.GasPrice) (int64, error)); ok { + return rf(ctx, destChainSelector, gasPrices) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []ccip.GasPrice) int64); ok { + r0 = rf(ctx, destChainSelector, gasPrices) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(int64) } - return r0 + if rf, ok := ret.Get(1).(func(context.Context, uint64, []ccip.GasPrice) error); ok { + r1 = rf(ctx, destChainSelector, gasPrices) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -// ORM_InsertGasPricesForDestChain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InsertGasPricesForDestChain' -type ORM_InsertGasPricesForDestChain_Call struct { +// ORM_UpsertGasPricesForDestChain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertGasPricesForDestChain' +type ORM_UpsertGasPricesForDestChain_Call struct { *mock.Call } -// InsertGasPricesForDestChain is a helper method to define mock.On call +// UpsertGasPricesForDestChain is a helper method to define mock.On call // - ctx context.Context // - destChainSelector uint64 -// - jobId int32 -// - gasPrices []ccip.GasPriceUpdate -func (_e *ORM_Expecter) InsertGasPricesForDestChain(ctx interface{}, destChainSelector interface{}, jobId interface{}, gasPrices interface{}) *ORM_InsertGasPricesForDestChain_Call { - return &ORM_InsertGasPricesForDestChain_Call{Call: _e.mock.On("InsertGasPricesForDestChain", ctx, destChainSelector, jobId, gasPrices)} +// - gasPrices []ccip.GasPrice +func (_e *ORM_Expecter) UpsertGasPricesForDestChain(ctx interface{}, destChainSelector interface{}, gasPrices interface{}) *ORM_UpsertGasPricesForDestChain_Call { + return &ORM_UpsertGasPricesForDestChain_Call{Call: _e.mock.On("UpsertGasPricesForDestChain", ctx, destChainSelector, gasPrices)} } -func (_c *ORM_InsertGasPricesForDestChain_Call) Run(run func(ctx context.Context, destChainSelector uint64, jobId int32, gasPrices []ccip.GasPriceUpdate)) *ORM_InsertGasPricesForDestChain_Call { +func (_c *ORM_UpsertGasPricesForDestChain_Call) Run(run func(ctx context.Context, destChainSelector uint64, gasPrices []ccip.GasPrice)) *ORM_UpsertGasPricesForDestChain_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].(int32), args[3].([]ccip.GasPriceUpdate)) + run(args[0].(context.Context), args[1].(uint64), args[2].([]ccip.GasPrice)) }) return _c } -func (_c *ORM_InsertGasPricesForDestChain_Call) Return(_a0 error) *ORM_InsertGasPricesForDestChain_Call { - _c.Call.Return(_a0) +func (_c *ORM_UpsertGasPricesForDestChain_Call) Return(_a0 int64, _a1 error) *ORM_UpsertGasPricesForDestChain_Call { + _c.Call.Return(_a0, _a1) return _c } -func (_c *ORM_InsertGasPricesForDestChain_Call) RunAndReturn(run func(context.Context, uint64, int32, []ccip.GasPriceUpdate) error) *ORM_InsertGasPricesForDestChain_Call { +func (_c *ORM_UpsertGasPricesForDestChain_Call) RunAndReturn(run func(context.Context, uint64, []ccip.GasPrice) (int64, error)) *ORM_UpsertGasPricesForDestChain_Call { _c.Call.Return(run) return _c } -// InsertTokenPricesForDestChain provides a mock function with given fields: ctx, destChainSelector, jobId, tokenPrices -func (_m *ORM) InsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, tokenPrices []ccip.TokenPriceUpdate) error { - ret := _m.Called(ctx, destChainSelector, jobId, tokenPrices) +// UpsertTokenPricesForDestChain provides a mock function with given fields: ctx, destChainSelector, tokenPrices, interval +func (_m *ORM) UpsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, tokenPrices []ccip.TokenPrice, interval time.Duration) (int64, error) { + ret := _m.Called(ctx, destChainSelector, tokenPrices, interval) if len(ret) == 0 { - panic("no return value specified for InsertTokenPricesForDestChain") + panic("no return value specified for UpsertTokenPricesForDestChain") } - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, int32, []ccip.TokenPriceUpdate) error); ok { - r0 = rf(ctx, destChainSelector, jobId, tokenPrices) + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []ccip.TokenPrice, time.Duration) (int64, error)); ok { + return rf(ctx, destChainSelector, tokenPrices, interval) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []ccip.TokenPrice, time.Duration) int64); ok { + r0 = rf(ctx, destChainSelector, tokenPrices, interval) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(int64) } - return r0 + if rf, ok := ret.Get(1).(func(context.Context, uint64, []ccip.TokenPrice, time.Duration) error); ok { + r1 = rf(ctx, destChainSelector, tokenPrices, interval) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } -// ORM_InsertTokenPricesForDestChain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InsertTokenPricesForDestChain' -type ORM_InsertTokenPricesForDestChain_Call struct { +// ORM_UpsertTokenPricesForDestChain_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertTokenPricesForDestChain' +type ORM_UpsertTokenPricesForDestChain_Call struct { *mock.Call } -// InsertTokenPricesForDestChain is a helper method to define mock.On call +// UpsertTokenPricesForDestChain is a helper method to define mock.On call // - ctx context.Context // - destChainSelector uint64 -// - jobId int32 -// - tokenPrices []ccip.TokenPriceUpdate -func (_e *ORM_Expecter) InsertTokenPricesForDestChain(ctx interface{}, destChainSelector interface{}, jobId interface{}, tokenPrices interface{}) *ORM_InsertTokenPricesForDestChain_Call { - return &ORM_InsertTokenPricesForDestChain_Call{Call: _e.mock.On("InsertTokenPricesForDestChain", ctx, destChainSelector, jobId, tokenPrices)} +// - tokenPrices []ccip.TokenPrice +// - interval time.Duration +func (_e *ORM_Expecter) UpsertTokenPricesForDestChain(ctx interface{}, destChainSelector interface{}, tokenPrices interface{}, interval interface{}) *ORM_UpsertTokenPricesForDestChain_Call { + return &ORM_UpsertTokenPricesForDestChain_Call{Call: _e.mock.On("UpsertTokenPricesForDestChain", ctx, destChainSelector, tokenPrices, interval)} } -func (_c *ORM_InsertTokenPricesForDestChain_Call) Run(run func(ctx context.Context, destChainSelector uint64, jobId int32, tokenPrices []ccip.TokenPriceUpdate)) *ORM_InsertTokenPricesForDestChain_Call { +func (_c *ORM_UpsertTokenPricesForDestChain_Call) Run(run func(ctx context.Context, destChainSelector uint64, tokenPrices []ccip.TokenPrice, interval time.Duration)) *ORM_UpsertTokenPricesForDestChain_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(uint64), args[2].(int32), args[3].([]ccip.TokenPriceUpdate)) + run(args[0].(context.Context), args[1].(uint64), args[2].([]ccip.TokenPrice), args[3].(time.Duration)) }) return _c } -func (_c *ORM_InsertTokenPricesForDestChain_Call) Return(_a0 error) *ORM_InsertTokenPricesForDestChain_Call { - _c.Call.Return(_a0) +func (_c *ORM_UpsertTokenPricesForDestChain_Call) Return(_a0 int64, _a1 error) *ORM_UpsertTokenPricesForDestChain_Call { + _c.Call.Return(_a0, _a1) return _c } -func (_c *ORM_InsertTokenPricesForDestChain_Call) RunAndReturn(run func(context.Context, uint64, int32, []ccip.TokenPriceUpdate) error) *ORM_InsertTokenPricesForDestChain_Call { +func (_c *ORM_UpsertTokenPricesForDestChain_Call) RunAndReturn(run func(context.Context, uint64, []ccip.TokenPrice, time.Duration) (int64, error)) *ORM_UpsertTokenPricesForDestChain_Call { _c.Call.Return(run) return _c } diff --git a/core/services/ccip/observability.go b/core/services/ccip/observability.go new file mode 100644 index 00000000000..8a061893ce6 --- /dev/null +++ b/core/services/ccip/observability.go @@ -0,0 +1,115 @@ +package ccip + +import ( + "context" + "strconv" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var ( + sqlLatencyBuckets = []float64{ + float64(10 * time.Millisecond), + float64(20 * time.Millisecond), + float64(30 * time.Millisecond), + float64(40 * time.Millisecond), + float64(50 * time.Millisecond), + float64(70 * time.Millisecond), + float64(90 * time.Millisecond), + float64(100 * time.Millisecond), + float64(200 * time.Millisecond), + float64(300 * time.Millisecond), + float64(400 * time.Millisecond), + float64(500 * time.Millisecond), + float64(750 * time.Millisecond), + float64(1 * time.Second), + } + ccipQueryDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Name: "ccip_orm_query_duration", + Buckets: sqlLatencyBuckets, + }, []string{"query", "destChainSelector"}) + ccipQueryDatasets = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Name: "ccip_orm_dataset_size", + }, []string{"query", "destChainSelector"}) +) + +type observedORM struct { + ORM + queryDuration *prometheus.HistogramVec + datasetSize *prometheus.GaugeVec +} + +var _ ORM = (*observedORM)(nil) + +func NewObservedORM(ds sqlutil.DataSource, lggr logger.Logger) (*observedORM, error) { + delegate, err := NewORM(ds, lggr) + if err != nil { + return nil, err + } + + return &observedORM{ + ORM: delegate, + queryDuration: ccipQueryDuration, + datasetSize: ccipQueryDatasets, + }, nil +} + +func (o *observedORM) GetGasPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]GasPrice, error) { + return withObservedQueryAndResults(o, "GetGasPricesByDestChain", destChainSelector, func() ([]GasPrice, error) { + return o.ORM.GetGasPricesByDestChain(ctx, destChainSelector) + }) +} + +func (o *observedORM) GetTokenPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]TokenPrice, error) { + return withObservedQueryAndResults(o, "GetTokenPricesByDestChain", destChainSelector, func() ([]TokenPrice, error) { + return o.ORM.GetTokenPricesByDestChain(ctx, destChainSelector) + }) +} + +func (o *observedORM) UpsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, gasPrices []GasPrice) (int64, error) { + return withObservedQueryAndRowsAffected(o, "UpsertGasPricesForDestChain", destChainSelector, func() (int64, error) { + return o.ORM.UpsertGasPricesForDestChain(ctx, destChainSelector, gasPrices) + }) +} + +func (o *observedORM) UpsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, tokenPrices []TokenPrice, interval time.Duration) (int64, error) { + return withObservedQueryAndRowsAffected(o, "UpsertTokenPricesForDestChain", destChainSelector, func() (int64, error) { + return o.ORM.UpsertTokenPricesForDestChain(ctx, destChainSelector, tokenPrices, interval) + }) +} + +func withObservedQueryAndRowsAffected(o *observedORM, queryName string, chainSelector uint64, query func() (int64, error)) (int64, error) { + rowsAffected, err := withObservedQuery(o, queryName, chainSelector, query) + if err == nil { + o.datasetSize. + WithLabelValues(queryName, strconv.FormatUint(chainSelector, 10)). + Set(float64(rowsAffected)) + } + return rowsAffected, err +} + +func withObservedQueryAndResults[T any](o *observedORM, queryName string, chainSelector uint64, query func() ([]T, error)) ([]T, error) { + results, err := withObservedQuery(o, queryName, chainSelector, query) + if err == nil { + o.datasetSize. + WithLabelValues(queryName, strconv.FormatUint(chainSelector, 10)). + Set(float64(len(results))) + } + return results, err +} + +func withObservedQuery[T any](o *observedORM, queryName string, chainSelector uint64, query func() (T, error)) (T, error) { + queryStarted := time.Now() + defer func() { + o.queryDuration. + WithLabelValues(queryName, strconv.FormatUint(chainSelector, 10)). + Observe(float64(time.Since(queryStarted))) + }() + return query() +} diff --git a/core/services/ccip/observability_test.go b/core/services/ccip/observability_test.go new file mode 100644 index 00000000000..24bfb4a9ec1 --- /dev/null +++ b/core/services/ccip/observability_test.go @@ -0,0 +1,94 @@ +package ccip + +import ( + "math/big" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" + io_prometheus_client "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func Test_MetricsAreTrackedForAllMethods(t *testing.T) { + ctx := testutils.Context(t) + db := pgtest.NewSqlxDB(t) + ccipORM, err := NewObservedORM(db, logger.TestLogger(t)) + require.NoError(t, err) + + tokenPrices := []TokenPrice{ + { + TokenAddr: "0xA", + TokenPrice: assets.NewWei(big.NewInt(1e18)), + }, + { + TokenAddr: "0xB", + TokenPrice: assets.NewWei(big.NewInt(1e18)), + }, + } + tokensUpserted, err := ccipORM.UpsertTokenPricesForDestChain(ctx, 100, tokenPrices, time.Second) + require.NoError(t, err) + assert.Equal(t, len(tokenPrices), int(tokensUpserted)) + assert.Equal(t, len(tokenPrices), counterFromGaugeByLabels(ccipORM.datasetSize, "UpsertTokenPricesForDestChain", "100")) + assert.Equal(t, 0, counterFromGaugeByLabels(ccipORM.datasetSize, "UpsertTokenPricesForDestChain", "200")) + + tokens, err := ccipORM.GetTokenPricesByDestChain(ctx, 100) + require.NoError(t, err) + assert.Equal(t, len(tokenPrices), len(tokens)) + assert.Equal(t, len(tokenPrices), counterFromGaugeByLabels(ccipORM.datasetSize, "GetTokenPricesByDestChain", "100")) + assert.Equal(t, 1, counterFromHistogramByLabels(t, ccipORM.queryDuration, "GetTokenPricesByDestChain", "100")) + + gasPrices := []GasPrice{ + { + SourceChainSelector: 200, + GasPrice: assets.NewWei(big.NewInt(1e18)), + }, + { + SourceChainSelector: 201, + GasPrice: assets.NewWei(big.NewInt(1e18)), + }, + { + SourceChainSelector: 202, + GasPrice: assets.NewWei(big.NewInt(1e18)), + }, + } + gasUpserted, err := ccipORM.UpsertGasPricesForDestChain(ctx, 100, gasPrices) + require.NoError(t, err) + assert.Equal(t, len(gasPrices), int(gasUpserted)) + assert.Equal(t, len(gasPrices), counterFromGaugeByLabels(ccipORM.datasetSize, "UpsertGasPricesForDestChain", "100")) + assert.Equal(t, 0, counterFromGaugeByLabels(ccipORM.datasetSize, "UpsertGasPricesForDestChain", "200")) + + gas, err := ccipORM.GetGasPricesByDestChain(ctx, 100) + require.NoError(t, err) + assert.Equal(t, len(gasPrices), len(gas)) + assert.Equal(t, len(gasPrices), counterFromGaugeByLabels(ccipORM.datasetSize, "GetGasPricesByDestChain", "100")) + assert.Equal(t, 1, counterFromHistogramByLabels(t, ccipORM.queryDuration, "GetGasPricesByDestChain", "100")) +} + +func counterFromHistogramByLabels(t *testing.T, histogramVec *prometheus.HistogramVec, labels ...string) int { + observer, err := histogramVec.GetMetricWithLabelValues(labels...) + require.NoError(t, err) + + metricCh := make(chan prometheus.Metric, 1) + observer.(prometheus.Histogram).Collect(metricCh) + close(metricCh) + + metric := <-metricCh + pb := &io_prometheus_client.Metric{} + err = metric.Write(pb) + require.NoError(t, err) + + return int(pb.GetHistogram().GetSampleCount()) +} + +func counterFromGaugeByLabels(gaugeVec *prometheus.GaugeVec, labels ...string) int { + value := testutil.ToFloat64(gaugeVec.WithLabelValues(labels...)) + return int(value) +} diff --git a/core/services/ccip/orm.go b/core/services/ccip/orm.go index d074ea7473e..1942c68fef9 100644 --- a/core/services/ccip/orm.go +++ b/core/services/ccip/orm.go @@ -8,65 +8,51 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/logger" ) type GasPrice struct { SourceChainSelector uint64 GasPrice *assets.Wei - CreatedAt time.Time -} - -type GasPriceUpdate struct { - SourceChainSelector uint64 - GasPrice *assets.Wei } type TokenPrice struct { TokenAddr string TokenPrice *assets.Wei - CreatedAt time.Time -} - -type TokenPriceUpdate struct { - TokenAddr string - TokenPrice *assets.Wei } type ORM interface { GetGasPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]GasPrice, error) GetTokenPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]TokenPrice, error) - InsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, gasPrices []GasPriceUpdate) error - InsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, tokenPrices []TokenPriceUpdate) error - - ClearGasPricesByDestChain(ctx context.Context, destChainSelector uint64, expireSec int) error - ClearTokenPricesByDestChain(ctx context.Context, destChainSelector uint64, expireSec int) error + UpsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, gasPrices []GasPrice) (int64, error) + UpsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, tokenPrices []TokenPrice, interval time.Duration) (int64, error) } type orm struct { - ds sqlutil.DataSource + ds sqlutil.DataSource + lggr logger.Logger } var _ ORM = (*orm)(nil) -func NewORM(ds sqlutil.DataSource) (ORM, error) { +func NewORM(ds sqlutil.DataSource, lggr logger.Logger) (ORM, error) { if ds == nil { return nil, fmt.Errorf("datasource to CCIP NewORM cannot be nil") } return &orm{ - ds: ds, + ds: ds, + lggr: lggr, }, nil } func (o *orm) GetGasPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]GasPrice, error) { var gasPrices []GasPrice stmt := ` - SELECT DISTINCT ON (source_chain_selector) - source_chain_selector, gas_price, created_at + SELECT source_chain_selector, gas_price FROM ccip.observed_gas_prices - WHERE chain_selector = $1 - ORDER BY source_chain_selector, created_at DESC; + WHERE chain_selector = $1; ` err := o.ds.SelectContext(ctx, &gasPrices, stmt, destChainSelector) if err != nil { @@ -79,82 +65,147 @@ func (o *orm) GetGasPricesByDestChain(ctx context.Context, destChainSelector uin func (o *orm) GetTokenPricesByDestChain(ctx context.Context, destChainSelector uint64) ([]TokenPrice, error) { var tokenPrices []TokenPrice stmt := ` - SELECT DISTINCT ON (token_addr) - token_addr, token_price, created_at + SELECT token_addr, token_price FROM ccip.observed_token_prices - WHERE chain_selector = $1 - ORDER BY token_addr, created_at DESC; + WHERE chain_selector = $1; ` err := o.ds.SelectContext(ctx, &tokenPrices, stmt, destChainSelector) if err != nil { return nil, err } - return tokenPrices, nil } -func (o *orm) InsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, gasPrices []GasPriceUpdate) error { +func (o *orm) UpsertGasPricesForDestChain(ctx context.Context, destChainSelector uint64, gasPrices []GasPrice) (int64, error) { if len(gasPrices) == 0 { - return nil + return 0, nil + } + + uniqueGasUpdates := make(map[string]GasPrice) + for _, gasPrice := range gasPrices { + key := fmt.Sprintf("%d-%d", gasPrice.SourceChainSelector, destChainSelector) + uniqueGasUpdates[key] = gasPrice } - insertData := make([]map[string]interface{}, 0, len(gasPrices)) - for _, price := range gasPrices { + insertData := make([]map[string]interface{}, 0, len(uniqueGasUpdates)) + for _, price := range uniqueGasUpdates { insertData = append(insertData, map[string]interface{}{ "chain_selector": destChainSelector, - "job_id": jobId, "source_chain_selector": price.SourceChainSelector, "gas_price": price.GasPrice, }) } - // using statement_timestamp() to make testing easier - stmt := `INSERT INTO ccip.observed_gas_prices (chain_selector, job_id, source_chain_selector, gas_price, created_at) - VALUES (:chain_selector, :job_id, :source_chain_selector, :gas_price, statement_timestamp());` - _, err := o.ds.NamedExecContext(ctx, stmt, insertData) + stmt := `INSERT INTO ccip.observed_gas_prices (chain_selector, source_chain_selector, gas_price, updated_at) + VALUES (:chain_selector, :source_chain_selector, :gas_price, statement_timestamp()) + ON CONFLICT (source_chain_selector, chain_selector) + DO UPDATE SET gas_price = EXCLUDED.gas_price, updated_at = EXCLUDED.updated_at;` + + result, err := o.ds.NamedExecContext(ctx, stmt, insertData) if err != nil { - err = fmt.Errorf("error inserting gas prices for job %d: %w", jobId, err) + return 0, fmt.Errorf("error inserting gas prices %w", err) } - - return err + return result.RowsAffected() } -func (o *orm) InsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, jobId int32, tokenPrices []TokenPriceUpdate) error { +// UpsertTokenPricesForDestChain inserts or updates only relevant token prices. +// In order to reduce locking an unnecessary writes to the table, we start with fetching current prices. +// If price for a token doesn't change or was updated recently we don't include that token to the upsert query. +// We don't run in TX intentionally, because we don't want to lock the table and conflicts are resolved on the insert level +func (o *orm) UpsertTokenPricesForDestChain(ctx context.Context, destChainSelector uint64, tokenPrices []TokenPrice, interval time.Duration) (int64, error) { if len(tokenPrices) == 0 { - return nil + return 0, nil } - insertData := make([]map[string]interface{}, 0, len(tokenPrices)) - for _, price := range tokenPrices { + tokensToUpdate, err := o.pickOnlyRelevantTokensForUpdate(ctx, destChainSelector, tokenPrices, interval) + if err != nil || len(tokensToUpdate) == 0 { + return 0, err + } + + insertData := make([]map[string]interface{}, 0, len(tokensToUpdate)) + for _, price := range tokensToUpdate { insertData = append(insertData, map[string]interface{}{ "chain_selector": destChainSelector, - "job_id": jobId, "token_addr": price.TokenAddr, "token_price": price.TokenPrice, }) } - // using statement_timestamp() to make testing easier - stmt := `INSERT INTO ccip.observed_token_prices (chain_selector, job_id, token_addr, token_price, created_at) - VALUES (:chain_selector, :job_id, :token_addr, :token_price, statement_timestamp());` - _, err := o.ds.NamedExecContext(ctx, stmt, insertData) + stmt := `INSERT INTO ccip.observed_token_prices (chain_selector, token_addr, token_price, updated_at) + VALUES (:chain_selector, :token_addr, :token_price, statement_timestamp()) + ON CONFLICT (token_addr, chain_selector) + DO UPDATE SET token_price = EXCLUDED.token_price, updated_at = EXCLUDED.updated_at;` + result, err := o.ds.NamedExecContext(ctx, stmt, insertData) if err != nil { - err = fmt.Errorf("error inserting token prices for job %d: %w", jobId, err) + return 0, fmt.Errorf("error inserting token prices %w", err) } - - return err + return result.RowsAffected() } -func (o *orm) ClearGasPricesByDestChain(ctx context.Context, destChainSelector uint64, expireSec int) error { - stmt := `DELETE FROM ccip.observed_gas_prices WHERE chain_selector = $1 AND created_at < (statement_timestamp() - $2 * interval '1 second')` +// pickOnlyRelevantTokensForUpdate returns only tokens that need to be updated. Multiple jobs can be updating the same tokens, +// in order to reduce table locking and redundant upserts we start with reading the table and checking which tokens are eligible for update. +// A token is eligible for update when time since last update is greater than the interval. +func (o *orm) pickOnlyRelevantTokensForUpdate( + ctx context.Context, + destChainSelector uint64, + tokenPrices []TokenPrice, + interval time.Duration, +) ([]TokenPrice, error) { + tokenPricesByAddress := toTokensByAddress(tokenPrices) + + // Picks only tokens which were recently updated and can be ignored, + // we will filter out these tokens from the upsert query. + stmt := ` + SELECT + token_addr + FROM ccip.observed_token_prices + WHERE + chain_selector = $1 + and token_addr = any($2) + and updated_at >= statement_timestamp() - $3::interval + ` + + pgInterval := fmt.Sprintf("%d milliseconds", interval.Milliseconds()) + args := []interface{}{destChainSelector, tokenAddrsToBytes(tokenPricesByAddress), pgInterval} + var dbTokensToIgnore []string + if err := o.ds.SelectContext(ctx, &dbTokensToIgnore, stmt, args...); err != nil { + return nil, err + } + + tokensToIgnore := make(map[string]struct{}, len(dbTokensToIgnore)) + for _, tk := range dbTokensToIgnore { + tokensToIgnore[tk] = struct{}{} + } - _, err := o.ds.ExecContext(ctx, stmt, destChainSelector, expireSec) - return err + tokenPricesToUpdate := make([]TokenPrice, 0, len(tokenPrices)) + for tokenAddr, tokenPrice := range tokenPricesByAddress { + eligibleForUpdate := false + if _, ok := tokensToIgnore[tokenAddr]; !ok { + eligibleForUpdate = true + tokenPricesToUpdate = append(tokenPricesToUpdate, TokenPrice{TokenAddr: tokenAddr, TokenPrice: tokenPrice}) + } + o.lggr.Debugw( + "Token price eligibility for database update", + "eligibleForUpdate", eligibleForUpdate, + "token", tokenAddr, + "price", tokenPrice, + ) + } + return tokenPricesToUpdate, nil } -func (o *orm) ClearTokenPricesByDestChain(ctx context.Context, destChainSelector uint64, expireSec int) error { - stmt := `DELETE FROM ccip.observed_token_prices WHERE chain_selector = $1 AND created_at < (statement_timestamp() - $2 * interval '1 second')` +func toTokensByAddress(tokens []TokenPrice) map[string]*assets.Wei { + tokensByAddr := make(map[string]*assets.Wei, len(tokens)) + for _, tk := range tokens { + tokensByAddr[tk.TokenAddr] = tk.TokenPrice + } + return tokensByAddr +} - _, err := o.ds.ExecContext(ctx, stmt, destChainSelector, expireSec) - return err +func tokenAddrsToBytes(tokens map[string]*assets.Wei) [][]byte { + addrs := make([][]byte, 0, len(tokens)) + for tkAddr := range tokens { + addrs = append(addrs, []byte(tkAddr)) + } + return addrs } diff --git a/core/services/ccip/orm_test.go b/core/services/ccip/orm_test.go index 7b7b8d82710..e778ddf6ce3 100644 --- a/core/services/ccip/orm_test.go +++ b/core/services/ccip/orm_test.go @@ -15,13 +15,18 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var ( + r = rand.New(rand.NewSource(time.Now().UnixNano())) ) func setupORM(t *testing.T) (ORM, sqlutil.DataSource) { t.Helper() db := pgtest.NewSqlxDB(t) - orm, err := NewORM(db) + orm, err := NewORM(db, logger.TestLogger(t)) require.NoError(t, err) @@ -37,12 +42,12 @@ func generateChainSelectors(n int) []uint64 { return selectors } -func generateGasPriceUpdates(chainSelector uint64, n int) []GasPriceUpdate { - updates := make([]GasPriceUpdate, n) +func generateGasPrices(chainSelector uint64, n int) []GasPrice { + updates := make([]GasPrice, n) for i := 0; i < n; i++ { // gas prices can take up whole range of uint256 uint256Max := new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1)) - row := GasPriceUpdate{ + row := GasPrice{ SourceChainSelector: chainSelector, GasPrice: assets.NewWei(new(big.Int).Sub(uint256Max, big.NewInt(int64(i)))), } @@ -61,10 +66,21 @@ func generateTokenAddresses(n int) []string { return addrs } -func generateTokenPriceUpdates(tokenAddr string, n int) []TokenPriceUpdate { - updates := make([]TokenPriceUpdate, n) +func generateRandomTokenPrices(tokenAddrs []string) []TokenPrice { + updates := make([]TokenPrice, 0, len(tokenAddrs)) + for _, addr := range tokenAddrs { + updates = append(updates, TokenPrice{ + TokenAddr: addr, + TokenPrice: assets.NewWei(new(big.Int).Rand(r, big.NewInt(1e18))), + }) + } + return updates +} + +func generateTokenPrices(tokenAddr string, n int) []TokenPrice { + updates := make([]TokenPrice, n) for i := 0; i < n; i++ { - row := TokenPriceUpdate{ + row := TokenPrice{ TokenAddr: tokenAddr, TokenPrice: assets.NewWei(new(big.Int).Mul(big.NewInt(1e18), big.NewInt(int64(i)))), } @@ -134,20 +150,20 @@ func TestORM_InsertAndGetGasPrices(t *testing.T) { sourceSelectors := generateChainSelectors(numSourceChainSelectors) - updates := make(map[uint64][]GasPriceUpdate) + updates := make(map[uint64][]GasPrice) for _, selector := range sourceSelectors { - updates[selector] = generateGasPriceUpdates(selector, numUpdatesPerSourceSelector) + updates[selector] = generateGasPrices(selector, numUpdatesPerSourceSelector) } // 5 jobs, each inserting prices for 10 chains, with 20 updates per chain. - expectedPrices := make(map[uint64]GasPriceUpdate) + expectedPrices := make(map[uint64]GasPrice) for i := 0; i < numJobs; i++ { for selector, updatesPerSelector := range updates { lastIndex := len(updatesPerSelector) - 1 - err := orm.InsertGasPricesForDestChain(ctx, destSelector, int32(i), updatesPerSelector[:lastIndex]) + _, err := orm.UpsertGasPricesForDestChain(ctx, destSelector, updatesPerSelector[:lastIndex]) assert.NoError(t, err) - err = orm.InsertGasPricesForDestChain(ctx, destSelector, int32(i), updatesPerSelector[lastIndex:]) + _, err = orm.UpsertGasPricesForDestChain(ctx, destSelector, updatesPerSelector[lastIndex:]) assert.NoError(t, err) expectedPrices[selector] = updatesPerSelector[lastIndex] @@ -156,7 +172,7 @@ func TestORM_InsertAndGetGasPrices(t *testing.T) { // verify number of rows inserted numRows := getGasTableRowCount(t, db) - assert.Equal(t, numJobs*numSourceChainSelectors*numUpdatesPerSourceSelector, numRows) + assert.Equal(t, numSourceChainSelectors, numRows) prices, err := orm.GetGasPricesByDestChain(ctx, destSelector) assert.NoError(t, err) @@ -170,15 +186,15 @@ func TestORM_InsertAndGetGasPrices(t *testing.T) { } // after the initial inserts, insert new round of prices, 1 price per selector this time - var combinedUpdates []GasPriceUpdate + var combinedUpdates []GasPrice for selector, updatesPerSelector := range updates { combinedUpdates = append(combinedUpdates, updatesPerSelector[0]) expectedPrices[selector] = updatesPerSelector[0] } - err = orm.InsertGasPricesForDestChain(ctx, destSelector, 1, combinedUpdates) + _, err = orm.UpsertGasPricesForDestChain(ctx, destSelector, combinedUpdates) assert.NoError(t, err) - assert.Equal(t, numJobs*numSourceChainSelectors*numUpdatesPerSourceSelector+numSourceChainSelectors, getGasTableRowCount(t, db)) + assert.Equal(t, numSourceChainSelectors, getGasTableRowCount(t, db)) prices, err = orm.GetGasPricesByDestChain(ctx, destSelector) assert.NoError(t, err) @@ -190,7 +206,7 @@ func TestORM_InsertAndGetGasPrices(t *testing.T) { } } -func TestORM_InsertAndDeleteGasPrices(t *testing.T) { +func TestORM_UpsertGasPrices(t *testing.T) { t.Parallel() ctx := testutils.Context(t) @@ -202,13 +218,13 @@ func TestORM_InsertAndDeleteGasPrices(t *testing.T) { sourceSelectors := generateChainSelectors(numSourceChainSelectors) - updates := make(map[uint64][]GasPriceUpdate) + updates := make(map[uint64][]GasPrice) for _, selector := range sourceSelectors { - updates[selector] = generateGasPriceUpdates(selector, numUpdatesPerSourceSelector) + updates[selector] = generateGasPrices(selector, numUpdatesPerSourceSelector) } for _, updatesPerSelector := range updates { - err := orm.InsertGasPricesForDestChain(ctx, destSelector, 1, updatesPerSelector) + _, err := orm.UpsertGasPricesForDestChain(ctx, destSelector, updatesPerSelector) assert.NoError(t, err) } @@ -217,21 +233,11 @@ func TestORM_InsertAndDeleteGasPrices(t *testing.T) { // insert for the 2nd time after interimTimeStamp for _, updatesPerSelector := range updates { - err := orm.InsertGasPricesForDestChain(ctx, destSelector, 1, updatesPerSelector) + _, err := orm.UpsertGasPricesForDestChain(ctx, destSelector, updatesPerSelector) assert.NoError(t, err) } - assert.Equal(t, 2*numSourceChainSelectors*numUpdatesPerSourceSelector, getGasTableRowCount(t, db)) - - // clear by sleepSec should delete rows inserted before it - err := orm.ClearGasPricesByDestChain(ctx, destSelector, sleepSec) - assert.NoError(t, err) - assert.Equal(t, numSourceChainSelectors*numUpdatesPerSourceSelector, getGasTableRowCount(t, db)) - - // clear by 0 expiration seconds should delete all rows - err = orm.ClearGasPricesByDestChain(ctx, destSelector, 0) - assert.NoError(t, err) - assert.Equal(t, 0, getGasTableRowCount(t, db)) + assert.Equal(t, numSourceChainSelectors, getGasTableRowCount(t, db)) } func TestORM_InsertAndGetTokenPrices(t *testing.T) { @@ -247,20 +253,20 @@ func TestORM_InsertAndGetTokenPrices(t *testing.T) { addrs := generateTokenAddresses(numAddresses) - updates := make(map[string][]TokenPriceUpdate) + updates := make(map[string][]TokenPrice) for _, addr := range addrs { - updates[addr] = generateTokenPriceUpdates(addr, numUpdatesPerAddress) + updates[addr] = generateTokenPrices(addr, numUpdatesPerAddress) } // 5 jobs, each inserting prices for 10 chains, with 20 updates per chain. - expectedPrices := make(map[string]TokenPriceUpdate) + expectedPrices := make(map[string]TokenPrice) for i := 0; i < numJobs; i++ { for addr, updatesPerAddr := range updates { lastIndex := len(updatesPerAddr) - 1 - err := orm.InsertTokenPricesForDestChain(ctx, destSelector, int32(i), updatesPerAddr[:lastIndex]) + _, err := orm.UpsertTokenPricesForDestChain(ctx, destSelector, updatesPerAddr[:lastIndex], 0) assert.NoError(t, err) - err = orm.InsertTokenPricesForDestChain(ctx, destSelector, int32(i), updatesPerAddr[lastIndex:]) + _, err = orm.UpsertTokenPricesForDestChain(ctx, destSelector, updatesPerAddr[lastIndex:], 0) assert.NoError(t, err) expectedPrices[addr] = updatesPerAddr[lastIndex] @@ -269,7 +275,7 @@ func TestORM_InsertAndGetTokenPrices(t *testing.T) { // verify number of rows inserted numRows := getTokenTableRowCount(t, db) - assert.Equal(t, numJobs*numAddresses*numUpdatesPerAddress, numRows) + assert.Equal(t, numAddresses, numRows) prices, err := orm.GetTokenPricesByDestChain(ctx, destSelector) assert.NoError(t, err) @@ -283,15 +289,15 @@ func TestORM_InsertAndGetTokenPrices(t *testing.T) { } // after the initial inserts, insert new round of prices, 1 price per selector this time - var combinedUpdates []TokenPriceUpdate + var combinedUpdates []TokenPrice for addr, updatesPerAddr := range updates { combinedUpdates = append(combinedUpdates, updatesPerAddr[0]) expectedPrices[addr] = updatesPerAddr[0] } - err = orm.InsertTokenPricesForDestChain(ctx, destSelector, 1, combinedUpdates) + _, err = orm.UpsertTokenPricesForDestChain(ctx, destSelector, combinedUpdates, 0) assert.NoError(t, err) - assert.Equal(t, numJobs*numAddresses*numUpdatesPerAddress+numAddresses, getTokenTableRowCount(t, db)) + assert.Equal(t, numAddresses, getTokenTableRowCount(t, db)) prices, err = orm.GetTokenPricesByDestChain(ctx, destSelector) assert.NoError(t, err) @@ -303,46 +309,68 @@ func TestORM_InsertAndGetTokenPrices(t *testing.T) { } } -func TestORM_InsertAndDeleteTokenPrices(t *testing.T) { +func TestORM_InsertTokenPricesWhenExpired(t *testing.T) { t.Parallel() ctx := testutils.Context(t) - - orm, db := setupORM(t) + orm, _ := setupORM(t) numAddresses := 10 - numUpdatesPerAddress := 20 - destSelector := uint64(1) - + destSelector := rand.Uint64() addrs := generateTokenAddresses(numAddresses) + initTokenUpdates := generateRandomTokenPrices(addrs) - updates := make(map[string][]TokenPriceUpdate) - for _, addr := range addrs { - updates[addr] = generateTokenPriceUpdates(addr, numUpdatesPerAddress) - } + // Insert the first time, table is initialized + rowsUpdated, err := orm.UpsertTokenPricesForDestChain(ctx, destSelector, initTokenUpdates, time.Minute) + require.NoError(t, err) + assert.Equal(t, int64(numAddresses), rowsUpdated) - for _, updatesPerAddr := range updates { - err := orm.InsertTokenPricesForDestChain(ctx, destSelector, 1, updatesPerAddr) - assert.NoError(t, err) - } + //time.Sleep(100 * time.Millisecond) - sleepSec := 2 - time.Sleep(time.Duration(sleepSec) * time.Second) + // Insert the second time, no updates, because prices haven't changed + rowsUpdated, err = orm.UpsertTokenPricesForDestChain(ctx, destSelector, initTokenUpdates, time.Minute) + require.NoError(t, err) + assert.Equal(t, int64(0), rowsUpdated) - // insert for the 2nd time after interimTimeStamp - for _, updatesPerAddr := range updates { - err := orm.InsertTokenPricesForDestChain(ctx, destSelector, 1, updatesPerAddr) - assert.NoError(t, err) + // There are new prices, but we still haven't reached interval + newPrices := generateRandomTokenPrices(addrs) + rowsUpdated, err = orm.UpsertTokenPricesForDestChain(ctx, destSelector, newPrices, time.Minute) + require.NoError(t, err) + assert.Equal(t, int64(0), rowsUpdated) + + time.Sleep(100 * time.Millisecond) + + // Again with the same new prices, but this time interval is reached + rowsUpdated, err = orm.UpsertTokenPricesForDestChain(ctx, destSelector, newPrices, time.Millisecond) + require.NoError(t, err) + assert.Equal(t, int64(numAddresses), rowsUpdated) + + dbTokenPrices, err := orm.GetTokenPricesByDestChain(ctx, destSelector) + require.NoError(t, err) + assert.Len(t, dbTokenPrices, numAddresses) + + dbTokenPricesByAddr := toTokensByAddress(dbTokenPrices) + for _, tkPrice := range newPrices { + dbToken, ok := dbTokenPricesByAddr[tkPrice.TokenAddr] + assert.True(t, ok) + assert.Equal(t, dbToken, tkPrice.TokenPrice) } +} - assert.Equal(t, 2*numAddresses*numUpdatesPerAddress, getTokenTableRowCount(t, db)) +func Benchmark_UpsertsTheSameTokenPrices(b *testing.B) { + db := pgtest.NewSqlxDB(b) + orm, err := NewORM(db, logger.NullLogger) + require.NoError(b, err) - // clear by sleepSec should delete rows inserted before it - err := orm.ClearTokenPricesByDestChain(ctx, destSelector, sleepSec) - assert.NoError(t, err) - assert.Equal(t, numAddresses*numUpdatesPerAddress, getTokenTableRowCount(t, db)) + ctx := testutils.Context(b) + numAddresses := 50 + destSelector := rand.Uint64() + addrs := generateTokenAddresses(numAddresses) + tokenUpdates := generateRandomTokenPrices(addrs) - // clear by 0 expiration seconds should delete all rows - err = orm.ClearTokenPricesByDestChain(ctx, destSelector, 0) - assert.NoError(t, err) - assert.Equal(t, 0, getTokenTableRowCount(t, db)) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err1 := orm.UpsertTokenPricesForDestChain(ctx, destSelector, tokenUpdates, time.Second) + require.NoError(b, err1) + } } diff --git a/core/services/chainlink/application.go b/core/services/chainlink/application.go index a3f46fc8a9f..3efc92c2270 100644 --- a/core/services/chainlink/application.go +++ b/core/services/chainlink/application.go @@ -257,6 +257,9 @@ func NewApplication(opts ApplicationOpts) (Application, error) { srvcs = append(srvcs, wfLauncher, registrySyncer) } + } else { + globalLogger.Debug("External registry not configured, skipping registry syncer and starting with an empty registry") + opts.CapabilitiesRegistry.SetLocalRegistry(&capabilities.TestMetadataRegistry{}) } // LOOPs can be created as options, in the case of LOOP relayers, or @@ -368,7 +371,7 @@ func NewApplication(opts ApplicationOpts) (Application, error) { for i, chain := range legacyEVMChains.Slice() { chainIDs[i] = chain.ID() } - telemReporter := headreporter.NewTelemetryReporter(telemetryManager, chainIDs...) + telemReporter := headreporter.NewTelemetryReporter(telemetryManager, globalLogger, chainIDs...) headReporter := headreporter.NewHeadReporterService(opts.DS, globalLogger, promReporter, telemReporter) srvcs = append(srvcs, headReporter) for _, chain := range legacyEVMChains.Slice() { diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 5fc9babe77b..b9d7921d783 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1304,7 +1304,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 10 errors: - ChainType: invalid value (Foo): must not be set with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - HeadTracker.HistoryDepth: invalid value (30): must be greater than or equal to FinalizedBlockOffset - GasEstimator.BumpThreshold: invalid value (0): cannot be 0 if auto-purge feature is enabled for Foo - Transactions.AutoPurge.Threshold: missing: needs to be set if auto-purge feature is enabled for Foo @@ -1317,7 +1317,7 @@ func TestConfig_Validate(t *testing.T) { - 2: 5 errors: - ChainType: invalid value (Arbitrum): only "optimismBedrock" can be used with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted - FinalityDepth: invalid value (0): must be greater than or equal to 1 - MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1 - 3.Nodes: 5 errors: diff --git a/core/services/feeds/service.go b/core/services/feeds/service.go index 1733d4a7582..5e8e743109a 100644 --- a/core/services/feeds/service.go +++ b/core/services/feeds/service.go @@ -209,7 +209,7 @@ func (s *service) RegisterManager(ctx context.Context, params RegisterManagerPar var txerr error id, txerr = tx.CreateManager(ctx, &mgr) - if err != nil { + if txerr != nil { return txerr } @@ -219,6 +219,9 @@ func (s *service) RegisterManager(ctx context.Context, params RegisterManagerPar return nil }) + if err != nil { + return 0, err + } privkey, err := s.getCSAPrivateKey() if err != nil { diff --git a/core/services/feeds/service_test.go b/core/services/feeds/service_test.go index 3ee7adc0d70..e98ae984fb7 100644 --- a/core/services/feeds/service_test.go +++ b/core/services/feeds/service_test.go @@ -265,6 +265,52 @@ func Test_Service_RegisterManager(t *testing.T) { assert.Equal(t, actual, id) } +func Test_Service_RegisterManager_InvalidCreateManager(t *testing.T) { + t.Parallel() + + var ( + id = int64(1) + pubKeyHex = "0f17c3bf72de8beef6e2d17a14c0a972f5d7e0e66e70722373f12b88382d40f9" + ) + + var pubKey crypto.PublicKey + _, err := hex.Decode([]byte(pubKeyHex), pubKey) + require.NoError(t, err) + + var ( + mgr = feeds.FeedsManager{ + Name: "FMS", + URI: "localhost:8080", + PublicKey: pubKey, + } + params = feeds.RegisterManagerParams{ + Name: "FMS", + URI: "localhost:8080", + PublicKey: pubKey, + } + ) + + svc := setupTestService(t) + + svc.orm.On("CountManagers", mock.Anything).Return(int64(0), nil) + svc.orm.On("CreateManager", mock.Anything, &mgr, mock.Anything). + Return(id, errors.New("orm error")) + // ListManagers runs in a goroutine so it might be called. + svc.orm.On("ListManagers", testutils.Context(t)).Return([]feeds.FeedsManager{mgr}, nil).Maybe() + + transactCall := svc.orm.On("Transact", mock.Anything, mock.Anything) + transactCall.Run(func(args mock.Arguments) { + fn := args[1].(func(orm feeds.ORM) error) + transactCall.ReturnArguments = mock.Arguments{fn(svc.orm)} + }) + _, err = svc.RegisterManager(testutils.Context(t), params) + // We need to stop the service because the manager will attempt to make a + // connection + svc.Close() + require.Error(t, err) + assert.Equal(t, "orm error", err.Error()) +} + func Test_Service_ListManagers(t *testing.T) { t.Parallel() ctx := testutils.Context(t) diff --git a/core/services/headreporter/head_reporter.go b/core/services/headreporter/head_reporter.go index f81a6acf913..94de8ae2be9 100644 --- a/core/services/headreporter/head_reporter.go +++ b/core/services/headreporter/head_reporter.go @@ -35,11 +35,12 @@ type ( func NewHeadReporterService(ds sqlutil.DataSource, lggr logger.Logger, reporters ...HeadReporter) *HeadReporterService { return &HeadReporterService{ - ds: ds, - lggr: lggr.Named("HeadReporter"), - newHeads: mailbox.NewSingle[*evmtypes.Head](), - chStop: make(chan struct{}), - reporters: reporters, + ds: ds, + lggr: lggr.Named("HeadReporter"), + newHeads: mailbox.NewSingle[*evmtypes.Head](), + chStop: make(chan struct{}), + reporters: reporters, + reportPeriod: 15 * time.Second, } } diff --git a/core/services/headreporter/head_reporter_test.go b/core/services/headreporter/head_reporter_test.go index ded7e1fb61b..304dd59a478 100644 --- a/core/services/headreporter/head_reporter_test.go +++ b/core/services/headreporter/head_reporter_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -42,4 +43,9 @@ func Test_HeadReporterService(t *testing.T) { require.Eventually(t, func() bool { return reportCalls.Load() == 2 }, 5*time.Second, 100*time.Millisecond) }) + + t.Run("has default report period", func(t *testing.T) { + service := NewHeadReporterService(pgtest.NewSqlxDB(t), logger.TestLogger(t), NewMockHeadReporter(t)) + assert.Equal(t, service.reportPeriod, 15*time.Second) + }) } diff --git a/core/services/headreporter/telemetry_reporter.go b/core/services/headreporter/telemetry_reporter.go index d76ce8a6044..93852f44c0d 100644 --- a/core/services/headreporter/telemetry_reporter.go +++ b/core/services/headreporter/telemetry_reporter.go @@ -10,21 +10,23 @@ import ( "google.golang.org/protobuf/proto" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" "github.com/smartcontractkit/chainlink/v2/core/services/telemetry" ) type telemetryReporter struct { + lggr logger.Logger endpoints map[uint64]commontypes.MonitoringEndpoint } -func NewTelemetryReporter(monitoringEndpointGen telemetry.MonitoringEndpointGenerator, chainIDs ...*big.Int) HeadReporter { +func NewTelemetryReporter(monitoringEndpointGen telemetry.MonitoringEndpointGenerator, lggr logger.Logger, chainIDs ...*big.Int) HeadReporter { endpoints := make(map[uint64]commontypes.MonitoringEndpoint) for _, chainID := range chainIDs { endpoints[chainID.Uint64()] = monitoringEndpointGen.GenMonitoringEndpoint("EVM", chainID.String(), "", synchronization.HeadReport) } - return &telemetryReporter{endpoints: endpoints} + return &telemetryReporter{lggr: lggr.Named("TelemetryReporter"), endpoints: endpoints} } func (t *telemetryReporter) ReportNewHead(ctx context.Context, head *evmtypes.Head) error { @@ -55,7 +57,8 @@ func (t *telemetryReporter) ReportNewHead(ctx context.Context, head *evmtypes.He } monitoringEndpoint.SendLog(bytes) if finalized == nil { - return errors.Errorf("No finalized block was found for chain_id=%d", head.EVMChainID.Int64()) + t.lggr.Infow("No finalized block was found", "chainID", head.EVMChainID.Int64(), + "head.number", head.Number, "chainLength", head.ChainLength()) } return nil } diff --git a/core/services/headreporter/telemetry_reporter_test.go b/core/services/headreporter/telemetry_reporter_test.go index c33edab0bcf..58c09354905 100644 --- a/core/services/headreporter/telemetry_reporter_test.go +++ b/core/services/headreporter/telemetry_reporter_test.go @@ -13,6 +13,7 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/headreporter" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization" "github.com/smartcontractkit/chainlink/v2/core/services/synchronization/telem" @@ -54,7 +55,7 @@ func Test_TelemetryReporter_NewHead(t *testing.T) { monitoringEndpointGen. On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). Return(monitoringEndpoint) - reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, big.NewInt(100)) + reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) err = reporter.ReportNewHead(testutils.Context(t), &head) assert.NoError(t, err) @@ -84,10 +85,10 @@ func Test_TelemetryReporter_NewHeadMissingFinalized(t *testing.T) { monitoringEndpointGen. On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). Return(monitoringEndpoint) - reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, big.NewInt(100)) + reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) err = reporter.ReportNewHead(testutils.Context(t), &head) - assert.Errorf(t, err, "No finalized block was found for chain_id=100") + assert.NoError(t, err) } func Test_TelemetryReporter_NewHead_MissingEndpoint(t *testing.T) { @@ -96,7 +97,7 @@ func Test_TelemetryReporter_NewHead_MissingEndpoint(t *testing.T) { On("GenMonitoringEndpoint", "EVM", "100", "", synchronization.HeadReport). Return(nil) - reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, big.NewInt(100)) + reporter := headreporter.NewTelemetryReporter(monitoringEndpointGen, logger.TestLogger(t), big.NewInt(100)) head := evmtypes.Head{Number: 42, EVMChainID: ubig.NewI(100)} diff --git a/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go b/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go index e964896ab93..771fdd322f3 100644 --- a/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go +++ b/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go @@ -69,7 +69,7 @@ func NewCommitServices(ctx context.Context, ds sqlutil.DataSource, srcProvider c commitStoreReader = ccip.NewProviderProxyCommitStoreReader(srcCommitStore, dstCommitStore) commitLggr := lggr.Named("CCIPCommit").With("sourceChain", sourceChainID, "destChain", destChainID) - var priceGetter pricegetter.PriceGetter + var priceGetter pricegetter.AllTokensPriceGetter withPipeline := strings.Trim(pluginConfig.TokenPricesUSDPipeline, "\n\t ") != "" if withPipeline { priceGetter, err = pricegetter.NewPipelineGetter(pluginConfig.TokenPricesUSDPipeline, pr, jb.ID, jb.ExternalJobID, jb.Name.ValueOrZero(), lggr) @@ -156,7 +156,7 @@ func NewCommitServices(ctx context.Context, ds sqlutil.DataSource, srcProvider c onRampAddress, ) - orm, err := cciporm.NewORM(ds) + orm, err := cciporm.NewObservedORM(ds, lggr) if err != nil { return nil, err } diff --git a/core/services/ocr2/plugins/ccip/clo_ccip_integration_test.go b/core/services/ocr2/plugins/ccip/clo_ccip_integration_test.go index 142ba006be6..2fddd58ac8f 100644 --- a/core/services/ocr2/plugins/ccip/clo_ccip_integration_test.go +++ b/core/services/ocr2/plugins/ccip/clo_ccip_integration_test.go @@ -1,6 +1,7 @@ package ccip_test import ( + "context" "encoding/json" "math/big" "testing" @@ -32,7 +33,6 @@ func Test_CLOSpecApprovalFlow_dynamicPriceGetter(t *testing.T) { ccipTH := integrationtesthelpers.SetupCCIPIntegrationTH(t, testhelpers.SourceChainID, testhelpers.SourceChainSelector, testhelpers.DestChainID, testhelpers.DestChainSelector) //Set up the aggregators here to avoid modifying ccipTH. - srcLinkAddr := ccipTH.Source.LinkToken.Address() dstLinkAddr := ccipTH.Dest.LinkToken.Address() srcNativeAddr, err := ccipTH.Source.Router.GetWrappedNative(nil) require.NoError(t, err) @@ -44,13 +44,6 @@ func Test_CLOSpecApprovalFlow_dynamicPriceGetter(t *testing.T) { require.NoError(t, err) ccipTH.Source.Chain.Commit() - aggSrcLnkAddr, _, aggSrcLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Source.User, ccipTH.Source.Chain, 18, big.NewInt(3e18)) - require.NoError(t, err) - ccipTH.Dest.Chain.Commit() - _, err = aggSrcLnk.UpdateRoundData(ccipTH.Source.User, big.NewInt(50), big.NewInt(8000000), big.NewInt(1000), big.NewInt(1000)) - require.NoError(t, err) - ccipTH.Source.Chain.Commit() - aggDstLnkAddr, _, aggDstLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Dest.User, ccipTH.Dest.Chain, 18, big.NewInt(3e18)) require.NoError(t, err) ccipTH.Dest.Chain.Commit() @@ -74,10 +67,6 @@ func Test_CLOSpecApprovalFlow_dynamicPriceGetter(t *testing.T) { priceGetterConfig := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - srcLinkAddr: { - ChainID: ccipTH.Source.ChainID, - AggregatorContractAddress: aggSrcLnkAddr, - }, srcNativeAddr: { ChainID: ccipTH.Source.ChainID, AggregatorContractAddress: aggSrcNatAddr, @@ -125,11 +114,22 @@ func test_CLOSpecApprovalFlow(t *testing.T, ccipTH integrationtesthelpers.CCIPIn _, err = ccipTH.Source.LinkToken.Approve(ccipTH.Source.User, ccipTH.Source.Router.Address(), new(big.Int).Set(fee)) require.NoError(t, err) - ccipTH.Source.Chain.Commit() + blockHash := ccipTH.Dest.Chain.Commit() + // get the block number + block, err := ccipTH.Dest.Chain.BlockByHash(context.Background(), blockHash) + require.NoError(t, err) + blockNumber := block.Number().Uint64() + 1 // +1 as a block will be mined for the request from EventuallyReportCommitted ccipTH.SendRequest(t, msg) ccipTH.AllNodesHaveReqSeqNum(t, currentSeqNum) ccipTH.EventuallyReportCommitted(t, currentSeqNum) + ccipTH.EventuallyPriceRegistryUpdated( + t, + blockNumber, + ccipTH.Source.ChainSelector, + []common.Address{ccipTH.Dest.LinkToken.Address(), ccipTH.Dest.WrappedNative.Address()}, + ccipTH.Source.WrappedNative.Address(), + ) executionLogs := ccipTH.AllNodesHaveExecutedSeqNums(t, currentSeqNum, currentSeqNum) assert.Len(t, executionLogs, 1) diff --git a/core/services/ocr2/plugins/ccip/integration_legacy_test.go b/core/services/ocr2/plugins/ccip/integration_legacy_test.go index 9bc94b5fe45..d89c50b4070 100644 --- a/core/services/ocr2/plugins/ccip/integration_legacy_test.go +++ b/core/services/ocr2/plugins/ccip/integration_legacy_test.go @@ -63,13 +63,6 @@ func TestIntegration_legacy_CCIP(t *testing.T) { require.NoError(t, err) ccipTH.Source.Chain.Commit() - aggSrcLnkAddr, _, aggSrcLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Source.User, ccipTH.Source.Chain, 18, big.NewInt(3e18)) - require.NoError(t, err) - ccipTH.Dest.Chain.Commit() - _, err = aggSrcLnk.UpdateRoundData(ccipTH.Source.User, big.NewInt(50), big.NewInt(8000000), big.NewInt(1000), big.NewInt(1000)) - require.NoError(t, err) - ccipTH.Source.Chain.Commit() - aggDstLnkAddr, _, aggDstLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Dest.User, ccipTH.Dest.Chain, 18, big.NewInt(3e18)) require.NoError(t, err) ccipTH.Dest.Chain.Commit() @@ -79,10 +72,6 @@ func TestIntegration_legacy_CCIP(t *testing.T) { priceGetterConfig := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - ccipTH.Source.LinkToken.Address(): { - ChainID: ccipTH.Source.ChainID, - AggregatorContractAddress: aggSrcLnkAddr, - }, ccipTH.Source.WrappedNative.Address(): { ChainID: ccipTH.Source.ChainID, AggregatorContractAddress: aggSrcNatAddr, diff --git a/core/services/ocr2/plugins/ccip/integration_test.go b/core/services/ocr2/plugins/ccip/integration_test.go index 202d2ef2304..bbf785efa8e 100644 --- a/core/services/ocr2/plugins/ccip/integration_test.go +++ b/core/services/ocr2/plugins/ccip/integration_test.go @@ -66,13 +66,6 @@ func TestIntegration_CCIP(t *testing.T) { require.NoError(t, err) ccipTH.Source.Chain.Commit() - aggSrcLnkAddr, _, aggSrcLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Source.User, ccipTH.Source.Chain, 18, big.NewInt(3e18)) - require.NoError(t, err) - ccipTH.Dest.Chain.Commit() - _, err = aggSrcLnk.UpdateRoundData(ccipTH.Source.User, big.NewInt(50), big.NewInt(8000000), big.NewInt(1000), big.NewInt(1000)) - require.NoError(t, err) - ccipTH.Source.Chain.Commit() - aggDstLnkAddr, _, aggDstLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Dest.User, ccipTH.Dest.Chain, 18, big.NewInt(3e18)) require.NoError(t, err) ccipTH.Dest.Chain.Commit() @@ -82,10 +75,6 @@ func TestIntegration_CCIP(t *testing.T) { priceGetterConfig := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - ccipTH.Source.LinkToken.Address(): { - ChainID: ccipTH.Source.ChainID, - AggregatorContractAddress: aggSrcLnkAddr, - }, ccipTH.Source.WrappedNative.Address(): { ChainID: ccipTH.Source.ChainID, AggregatorContractAddress: aggSrcNatAddr, diff --git a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go index 4f5ba6cfaea..8372ae47486 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go @@ -32,24 +32,7 @@ type BackfillArgs struct { SourceStartBlock, DestStartBlock uint64 } -// GetFilteredSortedLaneTokens returns union of tokens supported on this lane, including fee tokens from the provided price registry -// and the bridgeable tokens from offRamp. Bridgeable tokens are only included if they are configured on the pricegetter -// Fee tokens are not filtered as they must always be priced -func GetFilteredSortedLaneTokens(ctx context.Context, offRamp ccipdata.OffRampReader, priceRegistry cciptypes.PriceRegistryReader, priceGetter cciptypes.PriceGetter) (laneTokens []cciptypes.Address, excludedTokens []cciptypes.Address, err error) { - destFeeTokens, destBridgeableTokens, err := GetDestinationTokens(ctx, offRamp, priceRegistry) - if err != nil { - return nil, nil, fmt.Errorf("get tokens with batch limit: %w", err) - } - - destTokensWithPrice, destTokensWithoutPrice, err := priceGetter.FilterConfiguredTokens(ctx, destBridgeableTokens) - if err != nil { - return nil, nil, fmt.Errorf("filter for priced tokens: %w", err) - } - - return flattenedAndSortedTokens(destFeeTokens, destTokensWithPrice), destTokensWithoutPrice, nil -} - -func flattenedAndSortedTokens(slices ...[]cciptypes.Address) (tokens []cciptypes.Address) { +func FlattenedAndSortedTokens(slices ...[]cciptypes.Address) (tokens []cciptypes.Address) { // fee token can overlap with bridgeable tokens, we need to dedup them to arrive at lane token set tokens = FlattenUniqueSlice(slices...) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go index 73a3b834956..6f1cdb4a6af 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go @@ -3,22 +3,14 @@ package ccipcommon import ( "fmt" "math/rand" - "sort" "strconv" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" - ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/pricegetter" ) func TestGetMessageIDsAsHexString(t *testing.T) { @@ -70,77 +62,47 @@ func TestFlattenUniqueSlice(t *testing.T) { } } -func TestGetFilteredChainTokens(t *testing.T) { - const numTokens = 6 - var tokens []cciptypes.Address - for i := 0; i < numTokens; i++ { - tokens = append(tokens, ccipcalc.EvmAddrToGeneric(utils.RandomAddress())) - } - +func TestFlattenedAndSortedTokens(t *testing.T) { testCases := []struct { - name string - feeTokens []cciptypes.Address - destTokens []cciptypes.Address - expectedChainTokens []cciptypes.Address - expectedFilteredTokens []cciptypes.Address + name string + inputSlices [][]cciptypes.Address + expectedOutput []cciptypes.Address }{ + {name: "empty", inputSlices: nil, expectedOutput: []cciptypes.Address{}}, + {name: "empty 2", inputSlices: [][]cciptypes.Address{}, expectedOutput: []cciptypes.Address{}}, { - name: "empty", - feeTokens: []cciptypes.Address{}, - destTokens: []cciptypes.Address{}, - expectedChainTokens: []cciptypes.Address{}, - expectedFilteredTokens: []cciptypes.Address{}, + name: "single", + inputSlices: [][]cciptypes.Address{{"0x1", "0x2", "0x3"}}, + expectedOutput: []cciptypes.Address{"0x1", "0x2", "0x3"}, }, { - name: "unique tokens", - feeTokens: []cciptypes.Address{tokens[0]}, - destTokens: []cciptypes.Address{tokens[1], tokens[2], tokens[3]}, - expectedChainTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3]}, - expectedFilteredTokens: []cciptypes.Address{tokens[4], tokens[5]}, + name: "simple", + inputSlices: [][]cciptypes.Address{{"0x1", "0x2", "0x3"}, {"0x2", "0x3", "0x4"}}, + expectedOutput: []cciptypes.Address{"0x1", "0x2", "0x3", "0x4"}, }, { - name: "all tokens", - feeTokens: []cciptypes.Address{tokens[0]}, - destTokens: []cciptypes.Address{tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]}, - expectedChainTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]}, - expectedFilteredTokens: []cciptypes.Address{}, - }, - { - name: "overlapping tokens", - feeTokens: []cciptypes.Address{tokens[0]}, - destTokens: []cciptypes.Address{tokens[1], tokens[2], tokens[5], tokens[3], tokens[0], tokens[2], tokens[3], tokens[4], tokens[5], tokens[5]}, - expectedChainTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]}, - expectedFilteredTokens: []cciptypes.Address{}, - }, - { - name: "unconfigured tokens", - feeTokens: []cciptypes.Address{tokens[0]}, - destTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3], tokens[0], tokens[2], tokens[3], tokens[4], tokens[5], tokens[5]}, - expectedChainTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3], tokens[4]}, - expectedFilteredTokens: []cciptypes.Address{tokens[5]}, + name: "more complex case", + inputSlices: [][]cciptypes.Address{ + {"0x1", "0x3"}, + {"0x2", "0x4", "0x3"}, + {"0x5", "0x2", "0x7", "0xa"}, + }, + expectedOutput: []cciptypes.Address{ + "0x1", + "0x2", + "0x3", + "0x4", + "0x5", + "0x7", + "0xa", + }, }, } - ctx := testutils.Context(t) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - priceRegistry := ccipdatamocks.NewPriceRegistryReader(t) - priceRegistry.On("GetFeeTokens", ctx).Return(tc.feeTokens, nil).Once() - - priceGet := pricegetter.NewMockPriceGetter(t) - priceGet.On("FilterConfiguredTokens", mock.Anything, mock.Anything).Return(tc.expectedChainTokens, tc.expectedFilteredTokens, nil) - - offRamp := ccipdatamocks.NewOffRampReader(t) - offRamp.On("GetTokens", ctx).Return(cciptypes.OffRampTokens{DestinationTokens: tc.destTokens}, nil).Once() - - chainTokens, filteredTokens, err := GetFilteredSortedLaneTokens(ctx, offRamp, priceRegistry, priceGet) - assert.NoError(t, err) - - sort.Slice(tc.expectedChainTokens, func(i, j int) bool { - return tc.expectedChainTokens[i] < tc.expectedChainTokens[j] - }) - assert.Equal(t, tc.expectedChainTokens, chainTokens) - assert.Equal(t, tc.expectedFilteredTokens, filteredTokens) + res := FlattenedAndSortedTokens(tc.inputSlices...) + assert.Equal(t, tc.expectedOutput, res) }) } } diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go index 2118d5832da..2806c26e220 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "slices" "sort" "sync" "time" @@ -13,15 +14,16 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" cciporm "github.com/smartcontractkit/chainlink/v2/core/services/ccip" "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcommon" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/pricegetter" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices" + "github.com/smartcontractkit/chainlink/v2/core/utils" ) // PriceService manages DB access for gas and token price data. @@ -47,23 +49,11 @@ const ( // Token prices are refreshed every 10 minutes, we only report prices for blue chip tokens, DS&A simulation show // their prices are stable, 10-minute resolution is accurate enough. tokenPriceUpdateInterval = 10 * time.Minute - - // Prices should expire after 25 minutes in DB. Prices should be fresh in the Commit plugin. - // 25 min provides sufficient buffer for the Commit plugin to withstand transient price update outages, while - // surfacing price update outages quickly enough. - priceExpireThreshold = 25 * time.Minute - - // Cleanups are called every 10 minutes. For a given job, on average we may expect 3 token prices and 1 gas price. - // 10 minutes should result in ~13 rows being cleaned up per job, it is not a heavy load on DB, so there is no need - // to run cleanup more frequently. We shouldn't clean up less frequently than `priceExpireThreshold`. - priceCleanupInterval = 10 * time.Minute ) type priceService struct { - priceExpireThreshold time.Duration - cleanupInterval time.Duration - gasUpdateInterval time.Duration - tokenUpdateInterval time.Duration + gasUpdateInterval time.Duration + tokenUpdateInterval time.Duration lggr logger.Logger orm cciporm.ORM @@ -72,7 +62,7 @@ type priceService struct { sourceChainSelector uint64 sourceNative cciptypes.Address - priceGetter pricegetter.PriceGetter + priceGetter pricegetter.AllTokensPriceGetter offRampReader ccipdata.OffRampReader gasPriceEstimator prices.GasPriceEstimatorCommit destPriceRegistryReader ccipdata.PriceRegistryReader @@ -92,16 +82,14 @@ func NewPriceService( sourceChainSelector uint64, sourceNative cciptypes.Address, - priceGetter pricegetter.PriceGetter, + priceGetter pricegetter.AllTokensPriceGetter, offRampReader ccipdata.OffRampReader, ) PriceService { ctx, cancel := context.WithCancel(context.Background()) pw := &priceService{ - priceExpireThreshold: priceExpireThreshold, - cleanupInterval: utils.WithJitter(priceCleanupInterval), // use WithJitter to avoid multiple services impacting DB at same time - gasUpdateInterval: utils.WithJitter(gasPriceUpdateInterval), - tokenUpdateInterval: utils.WithJitter(tokenPriceUpdateInterval), + gasUpdateInterval: gasPriceUpdateInterval, + tokenUpdateInterval: tokenPriceUpdateInterval, lggr: lggr, orm: orm, @@ -140,13 +128,11 @@ func (p *priceService) Close() error { } func (p *priceService) run() { - cleanupTicker := time.NewTicker(p.cleanupInterval) - gasUpdateTicker := time.NewTicker(p.gasUpdateInterval) - tokenUpdateTicker := time.NewTicker(p.tokenUpdateInterval) + gasUpdateTicker := time.NewTicker(utils.WithJitter(p.gasUpdateInterval)) + tokenUpdateTicker := time.NewTicker(utils.WithJitter(p.tokenUpdateInterval)) go func() { defer p.wg.Done() - defer cleanupTicker.Stop() defer gasUpdateTicker.Stop() defer tokenUpdateTicker.Stop() @@ -154,11 +140,6 @@ func (p *priceService) run() { select { case <-p.backgroundCtx.Done(): return - case <-cleanupTicker.C: - err := p.runCleanup(p.backgroundCtx) - if err != nil { - p.lggr.Errorw("Error when cleaning up in-db prices in the background", "err", err) - } case <-gasUpdateTicker.C: err := p.runGasPriceUpdate(p.backgroundCtx) if err != nil { @@ -238,28 +219,6 @@ func (p *priceService) GetGasAndTokenPrices(ctx context.Context, destChainSelect return gasPrices, tokenPrices, nil } -func (p *priceService) runCleanup(ctx context.Context) error { - eg := new(errgroup.Group) - - eg.Go(func() error { - err := p.orm.ClearGasPricesByDestChain(ctx, p.destChainSelector, int(p.priceExpireThreshold.Seconds())) - if err != nil { - return fmt.Errorf("error clearing gas prices: %w", err) - } - return nil - }) - - eg.Go(func() error { - err := p.orm.ClearTokenPricesByDestChain(ctx, p.destChainSelector, int(p.priceExpireThreshold.Seconds())) - if err != nil { - return fmt.Errorf("error clearing token prices: %w", err) - } - return nil - }) - - return eg.Wait() -} - func (p *priceService) runGasPriceUpdate(ctx context.Context) error { // Protect against concurrent updates of `gasPriceEstimator` and `destPriceRegistryReader` // Price updates happen infrequently - once every `gasPriceUpdateInterval` seconds. @@ -322,6 +281,7 @@ func (p *priceService) observeGasPriceUpdates( // Include wrapped native to identify the source native USD price, notice USD is in 1e18 scale, i.e. $1 = 1e18 rawTokenPricesUSD, err := p.priceGetter.TokenPricesUSD(ctx, []cciptypes.Address{p.sourceNative}) + if err != nil { return nil, fmt.Errorf("failed to fetch source native price (%s): %w", p.sourceNative, err) } @@ -355,6 +315,9 @@ func (p *priceService) observeGasPriceUpdates( } // All prices are USD ($1=1e18) denominated. All prices must be not nil. +// Jobspec should have the destination tokens (Aggregate Rate Limit, Bps) and 1 source token (source native). +// Not respecting this will error out as we need to fetch the token decimals for all tokens expect sourceNative. +// destTokens is only used to check if sourceNative has the same address as one of the dest tokens. // Return token prices should contain the exact same tokens as in tokenDecimals. func (p *priceService) observeTokenPriceUpdates( ctx context.Context, @@ -363,35 +326,72 @@ func (p *priceService) observeTokenPriceUpdates( if p.destPriceRegistryReader == nil { return nil, fmt.Errorf("destPriceRegistry is not set yet") } - - sortedLaneTokens, filteredLaneTokens, err := ccipcommon.GetFilteredSortedLaneTokens(ctx, p.offRampReader, p.destPriceRegistryReader, p.priceGetter) + rawTokenPricesUSD, err := p.priceGetter.GetJobSpecTokenPricesUSD(ctx) if err != nil { - return nil, fmt.Errorf("get destination tokens: %w", err) + return nil, fmt.Errorf("failed to fetch token prices: %w", err) + } + + // Verify no price returned by price getter is nil + for token, price := range rawTokenPricesUSD { + if price == nil { + return nil, fmt.Errorf("Token price is nil for token %s", token) + } } - lggr.Debugw("Filtered bridgeable tokens with no configured price getter", "filteredLaneTokens", filteredLaneTokens) + lggr.Infow("Raw token prices", "rawTokenPrices", rawTokenPricesUSD) - queryTokens := ccipcommon.FlattenUniqueSlice(sortedLaneTokens) - rawTokenPricesUSD, err := p.priceGetter.TokenPricesUSD(ctx, queryTokens) + sourceNativeEvmAddr, err := ccipcalc.GenericAddrToEvm(p.sourceNative) if err != nil { - return nil, fmt.Errorf("failed to fetch token prices (%v): %w", queryTokens, err) + return nil, fmt.Errorf("failed to convert source native to EVM address: %w", err) } - lggr.Infow("Raw token prices", "rawTokenPrices", rawTokenPricesUSD) - // make sure that we got prices for all the tokens of our query - for _, token := range queryTokens { - if rawTokenPricesUSD[token] == nil { - return nil, fmt.Errorf("missing token price: %+v", token) + // Filter out source native token only if source native not in dest tokens + var finalDestTokens []cciptypes.Address + for token := range rawTokenPricesUSD { + tokenEvmAddr, err2 := ccipcalc.GenericAddrToEvm(token) + if err2 != nil { + return nil, fmt.Errorf("failed to convert token to EVM address: %w", err) + } + + if tokenEvmAddr != sourceNativeEvmAddr { + finalDestTokens = append(finalDestTokens, token) } } - destTokensDecimals, err := p.destPriceRegistryReader.GetTokensDecimals(ctx, sortedLaneTokens) + fee, bridged, err := ccipcommon.GetDestinationTokens(ctx, p.offRampReader, p.destPriceRegistryReader) + if err != nil { + return nil, fmt.Errorf("get destination tokens: %w", err) + } + onchainDestTokens := ccipcommon.FlattenedAndSortedTokens(fee, bridged) + lggr.Debugw("Destination tokens", "destTokens", onchainDestTokens) + + onchainTokensEvmAddr, err := ccipcalc.GenericAddrsToEvm(onchainDestTokens...) + if err != nil { + return nil, fmt.Errorf("failed to convert sorted lane tokens to EVM addresses: %w", err) + } + // Check for case where sourceNative has same address as one of the dest tokens (example: WETH in Base and Optimism) + hasSameDestAddress := slices.Contains(onchainTokensEvmAddr, sourceNativeEvmAddr) + + if hasSameDestAddress { + finalDestTokens = append(finalDestTokens, p.sourceNative) + } + + // Sort tokens to make the order deterministic, easier for testing and debugging + sort.Slice(finalDestTokens, func(i, j int) bool { + return finalDestTokens[i] < finalDestTokens[j] + }) + + destTokensDecimals, err := p.destPriceRegistryReader.GetTokensDecimals(ctx, finalDestTokens) if err != nil { return nil, fmt.Errorf("get tokens decimals: %w", err) } + if len(destTokensDecimals) != len(finalDestTokens) { + return nil, fmt.Errorf("mismatched token decimals and tokens") + } + tokenPricesUSD = make(map[cciptypes.Address]*big.Int, len(rawTokenPricesUSD)) - for i, token := range sortedLaneTokens { + for i, token := range finalDestTokens { tokenPricesUSD[token] = calculateUsdPer1e18TokenAmount(rawTokenPricesUSD[token], destTokensDecimals[i]) } @@ -403,28 +403,29 @@ func (p *priceService) observeTokenPriceUpdates( return tokenPricesUSD, nil } -func (p *priceService) writeGasPricesToDB(ctx context.Context, sourceGasPriceUSD *big.Int) (err error) { +func (p *priceService) writeGasPricesToDB(ctx context.Context, sourceGasPriceUSD *big.Int) error { if sourceGasPriceUSD == nil { return nil } - return p.orm.InsertGasPricesForDestChain(ctx, p.destChainSelector, p.jobId, []cciporm.GasPriceUpdate{ + _, err := p.orm.UpsertGasPricesForDestChain(ctx, p.destChainSelector, []cciporm.GasPrice{ { SourceChainSelector: p.sourceChainSelector, GasPrice: assets.NewWei(sourceGasPriceUSD), }, }) + return err } -func (p *priceService) writeTokenPricesToDB(ctx context.Context, tokenPricesUSD map[cciptypes.Address]*big.Int) (err error) { +func (p *priceService) writeTokenPricesToDB(ctx context.Context, tokenPricesUSD map[cciptypes.Address]*big.Int) error { if tokenPricesUSD == nil { return nil } - var tokenPrices []cciporm.TokenPriceUpdate + var tokenPrices []cciporm.TokenPrice for token, price := range tokenPricesUSD { - tokenPrices = append(tokenPrices, cciporm.TokenPriceUpdate{ + tokenPrices = append(tokenPrices, cciporm.TokenPrice{ TokenAddr: string(token), TokenPrice: assets.NewWei(price), }) @@ -435,7 +436,8 @@ func (p *priceService) writeTokenPricesToDB(ctx context.Context, tokenPricesUSD return tokenPrices[i].TokenAddr < tokenPrices[j].TokenAddr }) - return p.orm.InsertTokenPricesForDestChain(ctx, p.destChainSelector, p.jobId, tokenPrices) + _, err := p.orm.UpsertTokenPricesForDestChain(ctx, p.destChainSelector, tokenPrices, p.tokenUpdateInterval) + return err } // Input price is USD per full token, with 18 decimal precision diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service_test.go index 26721bdf8e4..a25c5d3c47e 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service_test.go @@ -5,6 +5,7 @@ import ( "fmt" "math/big" "reflect" + "slices" "sort" "testing" "time" @@ -18,93 +19,16 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" - "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" "github.com/smartcontractkit/chainlink/v2/core/logger" cciporm "github.com/smartcontractkit/chainlink/v2/core/services/ccip" ccipmocks "github.com/smartcontractkit/chainlink/v2/core/services/ccip/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" - "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcommon" ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/pricegetter" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices" ) -func TestPriceService_priceCleanup(t *testing.T) { - lggr := logger.TestLogger(t) - jobId := int32(1) - destChainSelector := uint64(12345) - sourceChainSelector := uint64(67890) - - testCases := []struct { - name string - gasPriceError bool - tokenPriceError bool - expectedErr bool - }{ - { - name: "ORM called successfully", - gasPriceError: false, - tokenPriceError: false, - expectedErr: false, - }, - { - name: "gasPrice clear failed", - gasPriceError: true, - tokenPriceError: false, - expectedErr: true, - }, - { - name: "tokenPrice clear failed", - gasPriceError: false, - tokenPriceError: true, - expectedErr: true, - }, - { - name: "both ORM calls failed", - gasPriceError: true, - tokenPriceError: true, - expectedErr: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - ctx := tests.Context(t) - - var gasPricesError error - var tokenPricesError error - if tc.gasPriceError { - gasPricesError = fmt.Errorf("gas prices error") - } - if tc.tokenPriceError { - tokenPricesError = fmt.Errorf("token prices error") - } - - mockOrm := ccipmocks.NewORM(t) - mockOrm.On("ClearGasPricesByDestChain", ctx, destChainSelector, int(priceExpireThreshold.Seconds())).Return(gasPricesError).Once() - mockOrm.On("ClearTokenPricesByDestChain", ctx, destChainSelector, int(priceExpireThreshold.Seconds())).Return(tokenPricesError).Once() - - priceService := NewPriceService( - lggr, - mockOrm, - jobId, - destChainSelector, - sourceChainSelector, - "", - nil, - nil, - ).(*priceService) - err := priceService.runCleanup(ctx) - if tc.expectedErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - func TestPriceService_writeGasPrices(t *testing.T) { lggr := logger.TestLogger(t) jobId := int32(1) @@ -113,7 +37,7 @@ func TestPriceService_writeGasPrices(t *testing.T) { gasPrice := big.NewInt(1e18) - expectedGasPriceUpdate := []cciporm.GasPriceUpdate{ + expectedGasPriceUpdate := []cciporm.GasPrice{ { SourceChainSelector: sourceChainSelector, GasPrice: assets.NewWei(gasPrice), @@ -147,7 +71,7 @@ func TestPriceService_writeGasPrices(t *testing.T) { } mockOrm := ccipmocks.NewORM(t) - mockOrm.On("InsertGasPricesForDestChain", ctx, destChainSelector, jobId, expectedGasPriceUpdate).Return(gasPricesError).Once() + mockOrm.On("UpsertGasPricesForDestChain", ctx, destChainSelector, expectedGasPriceUpdate).Return(int64(0), gasPricesError).Once() priceService := NewPriceService( lggr, @@ -180,7 +104,7 @@ func TestPriceService_writeTokenPrices(t *testing.T) { "0x234": big.NewInt(3e18), } - expectedTokenPriceUpdate := []cciporm.TokenPriceUpdate{ + expectedTokenPriceUpdate := []cciporm.TokenPrice{ { TokenAddr: "0x123", TokenPrice: assets.NewWei(big.NewInt(2e18)), @@ -218,7 +142,8 @@ func TestPriceService_writeTokenPrices(t *testing.T) { } mockOrm := ccipmocks.NewORM(t) - mockOrm.On("InsertTokenPricesForDestChain", ctx, destChainSelector, jobId, expectedTokenPriceUpdate).Return(tokenPricesError).Once() + mockOrm.On("UpsertTokenPricesForDestChain", ctx, destChainSelector, expectedTokenPriceUpdate, tokenPriceUpdateInterval). + Return(int64(len(expectedTokenPriceUpdate)), tokenPricesError).Once() priceService := NewPriceService( lggr, @@ -314,7 +239,7 @@ func TestPriceService_observeGasPriceUpdates(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - priceGetter := pricegetter.NewMockPriceGetter(t) + priceGetter := pricegetter.NewMockAllTokensPriceGetter(t) defer priceGetter.AssertExpectations(t) gasPriceEstimator := prices.NewMockGasPriceEstimatorCommit(t) @@ -358,6 +283,7 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) { jobId := int32(1) destChainSelector := uint64(12345) sourceChainSelector := uint64(67890) + sourceNativeToken := cciptypes.Address(utils.RandomAddress().String()) const nTokens = 10 tokens := make([]cciptypes.Address, nTokens) @@ -368,29 +294,50 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) { testCases := []struct { name string + destTokens []cciptypes.Address tokenDecimals map[cciptypes.Address]uint8 + sourceNativeToken cciptypes.Address filterOutTokens []cciptypes.Address priceGetterRespData map[cciptypes.Address]*big.Int priceGetterRespErr error expTokenPricesUSD map[cciptypes.Address]*big.Int expErr bool + expDecimalErr bool }{ { - name: "base", + name: "base case with src native token not equals to dest token", + tokenDecimals: map[cciptypes.Address]uint8{ // only destination tokens + tokens[1]: 18, + tokens[2]: 12, + }, + sourceNativeToken: sourceNativeToken, + priceGetterRespData: map[cciptypes.Address]*big.Int{ // should return all tokens (including source native token) + sourceNativeToken: val1e18(100), + tokens[1]: val1e18(200), + tokens[2]: val1e18(300), + }, + priceGetterRespErr: nil, + expTokenPricesUSD: map[cciptypes.Address]*big.Int{ // should only return the tokens in destination chain + tokens[1]: val1e18(200), + tokens[2]: val1e18(300 * 1e6), + }, + expErr: false, + }, + { + name: "base case with src native token equals to dest token", tokenDecimals: map[cciptypes.Address]uint8{ - tokens[0]: 18, - tokens[1]: 12, + sourceNativeToken: 18, + tokens[1]: 12, }, - filterOutTokens: []cciptypes.Address{tokens[2]}, + sourceNativeToken: sourceNativeToken, priceGetterRespData: map[cciptypes.Address]*big.Int{ - tokens[0]: val1e18(100), - tokens[1]: val1e18(200), - tokens[2]: val1e18(300), // price getter returned a price for this token even though we didn't request it (should be skipped) + sourceNativeToken: val1e18(100), + tokens[1]: val1e18(200), }, priceGetterRespErr: nil, expTokenPricesUSD: map[cciptypes.Address]*big.Int{ - tokens[0]: val1e18(100), - tokens[1]: val1e18(200 * 1e6), + sourceNativeToken: val1e18(100), + tokens[1]: val1e18(200 * 1e6), }, expErr: false, }, @@ -400,29 +347,73 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) { tokens[0]: 18, tokens[1]: 18, }, + sourceNativeToken: tokens[0], priceGetterRespData: nil, priceGetterRespErr: fmt.Errorf("some random network error"), expErr: true, }, + { + name: "price getter returns more prices than destTokens", + destTokens: []cciptypes.Address{tokens[1]}, + tokenDecimals: map[cciptypes.Address]uint8{ + tokens[1]: 18, + tokens[2]: 12, + tokens[3]: 18, + }, + sourceNativeToken: sourceNativeToken, + priceGetterRespData: map[cciptypes.Address]*big.Int{ + sourceNativeToken: val1e18(100), + tokens[1]: val1e18(200), + tokens[2]: val1e18(300), + tokens[3]: val1e18(400), + }, + expTokenPricesUSD: map[cciptypes.Address]*big.Int{ + tokens[1]: val1e18(200), + tokens[2]: val1e18(300 * 1e6), + tokens[3]: val1e18(400), + }, + }, + { + name: "price getter returns more prices with missing decimals", + tokenDecimals: map[cciptypes.Address]uint8{ + tokens[1]: 18, + tokens[2]: 12, + }, + sourceNativeToken: sourceNativeToken, + priceGetterRespData: map[cciptypes.Address]*big.Int{ + sourceNativeToken: val1e18(100), + tokens[1]: val1e18(200), + tokens[2]: val1e18(300), + tokens[3]: val1e18(400), + }, + priceGetterRespErr: nil, + expErr: true, + expDecimalErr: true, + }, { name: "price getter skipped a requested price", tokenDecimals: map[cciptypes.Address]uint8{ tokens[0]: 18, - tokens[1]: 18, }, + sourceNativeToken: tokens[0], priceGetterRespData: map[cciptypes.Address]*big.Int{ tokens[0]: val1e18(100), }, priceGetterRespErr: nil, - expErr: true, + expTokenPricesUSD: map[cciptypes.Address]*big.Int{ + tokens[0]: val1e18(100), + }, + expErr: false, }, { name: "nil token price", tokenDecimals: map[cciptypes.Address]uint8{ tokens[0]: 18, tokens[1]: 18, + tokens[2]: 18, }, - filterOutTokens: []cciptypes.Address{tokens[2]}, + sourceNativeToken: tokens[0], + filterOutTokens: []cciptypes.Address{tokens[2]}, priceGetterRespData: map[cciptypes.Address]*big.Int{ tokens[0]: nil, tokens[1]: val1e18(200), @@ -434,27 +425,34 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - priceGetter := pricegetter.NewMockPriceGetter(t) + priceGetter := pricegetter.NewMockAllTokensPriceGetter(t) defer priceGetter.AssertExpectations(t) var destTokens []cciptypes.Address - for tk := range tc.tokenDecimals { - destTokens = append(destTokens, tk) + if len(tc.destTokens) == 0 { + for tk := range tc.tokenDecimals { + destTokens = append(destTokens, tk) + } + } else { + destTokens = tc.destTokens } - sort.Slice(destTokens, func(i, j int) bool { - return destTokens[i] < destTokens[j] + + finalDestTokens := make([]cciptypes.Address, 0, len(destTokens)) + for addr := range tc.priceGetterRespData { + if (tc.sourceNativeToken != addr) || (slices.Contains(destTokens, addr)) { + finalDestTokens = append(finalDestTokens, addr) + } + } + sort.Slice(finalDestTokens, func(i, j int) bool { + return finalDestTokens[i] < finalDestTokens[j] }) + var destDecimals []uint8 - for _, token := range destTokens { + for _, token := range finalDestTokens { destDecimals = append(destDecimals, tc.tokenDecimals[token]) } - queryTokens := ccipcommon.FlattenUniqueSlice(destTokens) - - if len(queryTokens) > 0 { - priceGetter.On("TokenPricesUSD", mock.Anything, queryTokens).Return(tc.priceGetterRespData, tc.priceGetterRespErr) - priceGetter.On("FilterConfiguredTokens", mock.Anything, mock.Anything).Return(destTokens, tc.filterOutTokens, nil) - } + priceGetter.On("GetJobSpecTokenPricesUSD", mock.Anything).Return(tc.priceGetterRespData, tc.priceGetterRespErr) offRampReader := ccipdatamocks.NewOffRampReader(t) offRampReader.On("GetTokens", mock.Anything).Return(cciptypes.OffRampTokens{ @@ -462,7 +460,11 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) { }, nil).Maybe() destPriceReg := ccipdatamocks.NewPriceRegistryReader(t) - destPriceReg.On("GetTokensDecimals", mock.Anything, destTokens).Return(destDecimals, nil).Maybe() + if tc.expDecimalErr { + destPriceReg.On("GetTokensDecimals", mock.Anything, finalDestTokens).Return([]uint8{}, fmt.Errorf("Token not found")).Maybe() + } else { + destPriceReg.On("GetTokensDecimals", mock.Anything, finalDestTokens).Return(destDecimals, nil).Maybe() + } destPriceReg.On("GetFeeTokens", mock.Anything).Return([]cciptypes.Address{destTokens[0]}, nil).Maybe() priceService := NewPriceService( @@ -471,7 +473,7 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) { jobId, destChainSelector, sourceChainSelector, - "0x123", + tc.sourceNativeToken, priceGetter, offRampReader, ).(*priceService) @@ -725,7 +727,7 @@ func setupORM(t *testing.T) cciporm.ORM { t.Helper() db := pgtest.NewSqlxDB(t) - orm, err := cciporm.NewORM(db) + orm, err := cciporm.NewORM(db, logger.TestLogger(t)) require.NoError(t, err) @@ -747,46 +749,55 @@ func checkResultLen(t *testing.T, priceService PriceService, destChainSelector u return nil } -func TestPriceService_priceWriteAndCleanupInBackground(t *testing.T) { +func TestPriceService_priceWriteInBackground(t *testing.T) { lggr := logger.TestLogger(t) jobId := int32(1) destChainSelector := uint64(12345) sourceChainSelector := uint64(67890) ctx := tests.Context(t) - sourceNative := cciptypes.Address("0x123") - feeTokens := []cciptypes.Address{"0x234"} - rampTokens := []cciptypes.Address{"0x345", "0x456"} - rampFilteredTokens := []cciptypes.Address{"0x345"} - rampFilterOutTokens := []cciptypes.Address{"0x456"} + sourceNative := cciptypes.Address(utils.RandomAddress().String()) + feeToken := cciptypes.Address(utils.RandomAddress().String()) + destToken1 := cciptypes.Address(utils.RandomAddress().String()) + destToken2 := cciptypes.Address(utils.RandomAddress().String()) + + feeTokens := []cciptypes.Address{feeToken} + rampTokens := []cciptypes.Address{destToken1, destToken2} - laneTokens := []cciptypes.Address{"0x234", "0x345"} - laneTokenDecimals := []uint8{18, 18} + laneTokens := []cciptypes.Address{sourceNative, feeToken, destToken1, destToken2} + // sort laneTokens + sort.Slice(laneTokens, func(i, j int) bool { + return laneTokens[i] < laneTokens[j] + }) + laneTokenDecimals := []uint8{18, 18, 18, 18} - tokens := []cciptypes.Address{sourceNative, "0x234", "0x345"} - tokenPrices := []int64{2, 3, 4} + tokens := []cciptypes.Address{sourceNative, feeToken, destToken1, destToken2} + tokenPrices := []int64{2, 3, 4, 5} gasPrice := big.NewInt(10) orm := setupORM(t) - priceGetter := pricegetter.NewMockPriceGetter(t) + priceGetter := pricegetter.NewMockAllTokensPriceGetter(t) defer priceGetter.AssertExpectations(t) gasPriceEstimator := prices.NewMockGasPriceEstimatorCommit(t) defer gasPriceEstimator.AssertExpectations(t) - priceGetter.On("TokenPricesUSD", mock.Anything, tokens[:1]).Return(map[cciptypes.Address]*big.Int{ + priceGetter.On("TokenPricesUSD", mock.Anything, []cciptypes.Address{sourceNative}).Return(map[cciptypes.Address]*big.Int{ tokens[0]: val1e18(tokenPrices[0]), }, nil) - priceGetter.On("TokenPricesUSD", mock.Anything, tokens[1:]).Return(map[cciptypes.Address]*big.Int{ + + priceGetter.On("GetJobSpecTokenPricesUSD", mock.Anything).Return(map[cciptypes.Address]*big.Int{ + tokens[0]: val1e18(tokenPrices[0]), tokens[1]: val1e18(tokenPrices[1]), tokens[2]: val1e18(tokenPrices[2]), + tokens[3]: val1e18(tokenPrices[3]), }, nil) - priceGetter.On("FilterConfiguredTokens", mock.Anything, rampTokens).Return(rampFilteredTokens, rampFilterOutTokens, nil) + destTokens := append(rampTokens, sourceNative) offRampReader := ccipdatamocks.NewOffRampReader(t) offRampReader.On("GetTokens", mock.Anything).Return(cciptypes.OffRampTokens{ - DestinationTokens: rampTokens, + DestinationTokens: destTokens, }, nil).Maybe() gasPriceEstimator.On("GetGasPrice", mock.Anything).Return(gasPrice, nil) @@ -810,16 +821,11 @@ func TestPriceService_priceWriteAndCleanupInBackground(t *testing.T) { gasUpdateInterval := 2000 * time.Millisecond tokenUpdateInterval := 5000 * time.Millisecond - cleanupInterval := 3000 * time.Millisecond // run gas price task every 2 second priceService.gasUpdateInterval = gasUpdateInterval // run token price task every 5 second priceService.tokenUpdateInterval = tokenUpdateInterval - // run cleanup every 3 seconds - priceService.cleanupInterval = cleanupInterval - // expire all prices during every cleanup - priceService.priceExpireThreshold = time.Duration(0) // initially, db is empty assert.NoError(t, checkResultLen(t, priceService, destChainSelector, 0, 0)) @@ -832,24 +838,5 @@ func TestPriceService_priceWriteAndCleanupInBackground(t *testing.T) { assert.NoError(t, err) assert.NoError(t, checkResultLen(t, priceService, destChainSelector, 1, len(laneTokens))) - // eventually prices will be cleaned - assert.Eventually(t, func() bool { - err := checkResultLen(t, priceService, destChainSelector, 0, 0) - return err == nil - }, testutils.WaitTimeout(t), testutils.TestInterval) - - // then prices will be updated again - assert.Eventually(t, func() bool { - err := checkResultLen(t, priceService, destChainSelector, 1, len(laneTokens)) - return err == nil - }, testutils.WaitTimeout(t), testutils.TestInterval) - assert.NoError(t, priceService.Close()) - assert.NoError(t, priceService.runCleanup(ctx)) - - // after stopping PriceService and runCleanup, no more updates are inserted - for i := 0; i < 5; i++ { - time.Sleep(time.Second) - assert.NoError(t, checkResultLen(t, priceService, destChainSelector, 0, 0)) - } } diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/all_price_getter_mock.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/all_price_getter_mock.go new file mode 100644 index 00000000000..010c955c766 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/all_price_getter_mock.go @@ -0,0 +1,269 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package pricegetter + +import ( + context "context" + big "math/big" + + ccip "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + + mock "github.com/stretchr/testify/mock" +) + +// MockAllTokensPriceGetter is an autogenerated mock type for the AllTokensPriceGetter type +type MockAllTokensPriceGetter struct { + mock.Mock +} + +type MockAllTokensPriceGetter_Expecter struct { + mock *mock.Mock +} + +func (_m *MockAllTokensPriceGetter) EXPECT() *MockAllTokensPriceGetter_Expecter { + return &MockAllTokensPriceGetter_Expecter{mock: &_m.Mock} +} + +// Close provides a mock function with given fields: +func (_m *MockAllTokensPriceGetter) Close() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Close") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAllTokensPriceGetter_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type MockAllTokensPriceGetter_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +func (_e *MockAllTokensPriceGetter_Expecter) Close() *MockAllTokensPriceGetter_Close_Call { + return &MockAllTokensPriceGetter_Close_Call{Call: _e.mock.On("Close")} +} + +func (_c *MockAllTokensPriceGetter_Close_Call) Run(run func()) *MockAllTokensPriceGetter_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockAllTokensPriceGetter_Close_Call) Return(_a0 error) *MockAllTokensPriceGetter_Close_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAllTokensPriceGetter_Close_Call) RunAndReturn(run func() error) *MockAllTokensPriceGetter_Close_Call { + _c.Call.Return(run) + return _c +} + +// FilterConfiguredTokens provides a mock function with given fields: ctx, tokens +func (_m *MockAllTokensPriceGetter) FilterConfiguredTokens(ctx context.Context, tokens []ccip.Address) ([]ccip.Address, []ccip.Address, error) { + ret := _m.Called(ctx, tokens) + + if len(ret) == 0 { + panic("no return value specified for FilterConfiguredTokens") + } + + var r0 []ccip.Address + var r1 []ccip.Address + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []ccip.Address) ([]ccip.Address, []ccip.Address, error)); ok { + return rf(ctx, tokens) + } + if rf, ok := ret.Get(0).(func(context.Context, []ccip.Address) []ccip.Address); ok { + r0 = rf(ctx, tokens) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]ccip.Address) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []ccip.Address) []ccip.Address); ok { + r1 = rf(ctx, tokens) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]ccip.Address) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, []ccip.Address) error); ok { + r2 = rf(ctx, tokens) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockAllTokensPriceGetter_FilterConfiguredTokens_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FilterConfiguredTokens' +type MockAllTokensPriceGetter_FilterConfiguredTokens_Call struct { + *mock.Call +} + +// FilterConfiguredTokens is a helper method to define mock.On call +// - ctx context.Context +// - tokens []ccip.Address +func (_e *MockAllTokensPriceGetter_Expecter) FilterConfiguredTokens(ctx interface{}, tokens interface{}) *MockAllTokensPriceGetter_FilterConfiguredTokens_Call { + return &MockAllTokensPriceGetter_FilterConfiguredTokens_Call{Call: _e.mock.On("FilterConfiguredTokens", ctx, tokens)} +} + +func (_c *MockAllTokensPriceGetter_FilterConfiguredTokens_Call) Run(run func(ctx context.Context, tokens []ccip.Address)) *MockAllTokensPriceGetter_FilterConfiguredTokens_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]ccip.Address)) + }) + return _c +} + +func (_c *MockAllTokensPriceGetter_FilterConfiguredTokens_Call) Return(configured []ccip.Address, unconfigured []ccip.Address, err error) *MockAllTokensPriceGetter_FilterConfiguredTokens_Call { + _c.Call.Return(configured, unconfigured, err) + return _c +} + +func (_c *MockAllTokensPriceGetter_FilterConfiguredTokens_Call) RunAndReturn(run func(context.Context, []ccip.Address) ([]ccip.Address, []ccip.Address, error)) *MockAllTokensPriceGetter_FilterConfiguredTokens_Call { + _c.Call.Return(run) + return _c +} + +// GetJobSpecTokenPricesUSD provides a mock function with given fields: ctx +func (_m *MockAllTokensPriceGetter) GetJobSpecTokenPricesUSD(ctx context.Context) (map[ccip.Address]*big.Int, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetJobSpecTokenPricesUSD") + } + + var r0 map[ccip.Address]*big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (map[ccip.Address]*big.Int, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) map[ccip.Address]*big.Int); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[ccip.Address]*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetJobSpecTokenPricesUSD' +type MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call struct { + *mock.Call +} + +// GetJobSpecTokenPricesUSD is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockAllTokensPriceGetter_Expecter) GetJobSpecTokenPricesUSD(ctx interface{}) *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call { + return &MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call{Call: _e.mock.On("GetJobSpecTokenPricesUSD", ctx)} +} + +func (_c *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call) Run(run func(ctx context.Context)) *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call) Return(_a0 map[ccip.Address]*big.Int, _a1 error) *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call) RunAndReturn(run func(context.Context) (map[ccip.Address]*big.Int, error)) *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call { + _c.Call.Return(run) + return _c +} + +// TokenPricesUSD provides a mock function with given fields: ctx, tokens +func (_m *MockAllTokensPriceGetter) TokenPricesUSD(ctx context.Context, tokens []ccip.Address) (map[ccip.Address]*big.Int, error) { + ret := _m.Called(ctx, tokens) + + if len(ret) == 0 { + panic("no return value specified for TokenPricesUSD") + } + + var r0 map[ccip.Address]*big.Int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []ccip.Address) (map[ccip.Address]*big.Int, error)); ok { + return rf(ctx, tokens) + } + if rf, ok := ret.Get(0).(func(context.Context, []ccip.Address) map[ccip.Address]*big.Int); ok { + r0 = rf(ctx, tokens) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[ccip.Address]*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []ccip.Address) error); ok { + r1 = rf(ctx, tokens) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockAllTokensPriceGetter_TokenPricesUSD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TokenPricesUSD' +type MockAllTokensPriceGetter_TokenPricesUSD_Call struct { + *mock.Call +} + +// TokenPricesUSD is a helper method to define mock.On call +// - ctx context.Context +// - tokens []ccip.Address +func (_e *MockAllTokensPriceGetter_Expecter) TokenPricesUSD(ctx interface{}, tokens interface{}) *MockAllTokensPriceGetter_TokenPricesUSD_Call { + return &MockAllTokensPriceGetter_TokenPricesUSD_Call{Call: _e.mock.On("TokenPricesUSD", ctx, tokens)} +} + +func (_c *MockAllTokensPriceGetter_TokenPricesUSD_Call) Run(run func(ctx context.Context, tokens []ccip.Address)) *MockAllTokensPriceGetter_TokenPricesUSD_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]ccip.Address)) + }) + return _c +} + +func (_c *MockAllTokensPriceGetter_TokenPricesUSD_Call) Return(_a0 map[ccip.Address]*big.Int, _a1 error) *MockAllTokensPriceGetter_TokenPricesUSD_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockAllTokensPriceGetter_TokenPricesUSD_Call) RunAndReturn(run func(context.Context, []ccip.Address) (map[ccip.Address]*big.Int, error)) *MockAllTokensPriceGetter_TokenPricesUSD_Call { + _c.Call.Return(run) + return _c +} + +// NewMockAllTokensPriceGetter creates a new instance of MockAllTokensPriceGetter. 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 NewMockAllTokensPriceGetter(t interface { + mock.TestingT + Cleanup(func()) +}) *MockAllTokensPriceGetter { + mock := &MockAllTokensPriceGetter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go index ed54428bd9b..ac4002f53fb 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go @@ -103,6 +103,11 @@ func (d *DynamicPriceGetter) FilterConfiguredTokens(ctx context.Context, tokens return configured, unconfigured, nil } +// It returns the prices of all tokens defined in the price getter. +func (d *DynamicPriceGetter) GetJobSpecTokenPricesUSD(ctx context.Context) (map[cciptypes.Address]*big.Int, error) { + return d.TokenPricesUSD(ctx, d.getAllTokensDefined()) +} + // TokenPricesUSD implements the PriceGetter interface. // It returns static prices stored in the price getter, and batch calls aggregators (one per chain) to retrieve aggregator-based prices. func (d *DynamicPriceGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.Address) (map[cciptypes.Address]*big.Int, error) { @@ -116,6 +121,18 @@ func (d *DynamicPriceGetter) TokenPricesUSD(ctx context.Context, tokens []ccipty return prices, nil } +func (d *DynamicPriceGetter) getAllTokensDefined() []cciptypes.Address { + tokens := make([]cciptypes.Address, 0) + + for addr := range d.cfg.AggregatorPrices { + tokens = append(tokens, ccipcalc.EvmAddrToGeneric(addr)) + } + for addr := range d.cfg.StaticPrices { + tokens = append(tokens, ccipcalc.EvmAddrToGeneric(addr)) + } + return tokens +} + // performBatchCalls performs batch calls on all chains to retrieve token prices. func (d *DynamicPriceGetter) performBatchCalls(ctx context.Context, batchCallsPerChain map[uint64]*batchCallsForChain, prices map[cciptypes.Address]*big.Int) error { for chainID, batchCalls := range batchCallsPerChain { diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go index 673b9776c79..78de2699688 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go @@ -25,12 +25,27 @@ type testParameters struct { evmClients map[uint64]DynamicPriceGetterClient tokens []common.Address expectedTokenPrices map[common.Address]big.Int + expectedTokenPricesForAll map[common.Address]big.Int evmCallErr bool invalidConfigErrorExpected bool priceResolutionErrorExpected bool } -func TestDynamicPriceGetter(t *testing.T) { +var ( + TK1 common.Address + TK2 common.Address + TK3 common.Address + TK4 common.Address +) + +func init() { + TK1 = utils.RandomAddress() + TK2 = utils.RandomAddress() + TK3 = utils.RandomAddress() + TK4 = utils.RandomAddress() +} + +func TestDynamicPriceGetterWithEmptyInput(t *testing.T) { tests := []struct { name string param testParameters @@ -63,6 +78,22 @@ func TestDynamicPriceGetter(t *testing.T) { name: "batchCall_returns_err", param: testParamBatchCallReturnsErr(t), }, + { + name: "less_inputs_than_defined_prices", + param: testLessInputsThanDefinedPrices(t), + }, + { + name: "get_all_tokens_aggregator_and_static", + param: testGetAllTokensAggregatorAndStatic(t), + }, + { + name: "get_all_tokens_aggregator_only", + param: testGetAllTokensAggregatorOnly(t), + }, + { + name: "get_all_tokens_static_only", + param: testGetAllTokensStaticOnly(), + }, } for _, test := range tests { @@ -74,34 +105,31 @@ func TestDynamicPriceGetter(t *testing.T) { } require.NoError(t, err) ctx := testutils.Context(t) - // Check configured token - unconfiguredTk := cciptypes.Address(utils.RandomAddress().String()) - cfgTokens, uncfgTokens, err := pg.FilterConfiguredTokens(ctx, []cciptypes.Address{unconfiguredTk}) - require.NoError(t, err) - assert.Equal(t, []cciptypes.Address{}, cfgTokens) - assert.Equal(t, []cciptypes.Address{unconfiguredTk}, uncfgTokens) - // Build list of tokens to query. - tokens := make([]cciptypes.Address, 0, len(test.param.tokens)) - for _, tk := range test.param.tokens { - tokenAddr := ccipcalc.EvmAddrToGeneric(tk) - tokens = append(tokens, tokenAddr) - } - prices, err := pg.TokenPricesUSD(ctx, tokens) - if test.param.evmCallErr { - require.Error(t, err) - return - } + var prices map[cciptypes.Address]*big.Int + var expectedTokens map[common.Address]big.Int + if len(test.param.expectedTokenPricesForAll) == 0 { + prices, err = pg.TokenPricesUSD(ctx, ccipcalc.EvmAddrsToGeneric(test.param.tokens...)) + if test.param.evmCallErr { + require.Error(t, err) + return + } - if test.param.priceResolutionErrorExpected { - require.Error(t, err) - return + if test.param.priceResolutionErrorExpected { + require.Error(t, err) + return + } + expectedTokens = test.param.expectedTokenPrices + } else { + prices, err = pg.GetJobSpecTokenPricesUSD(ctx) + expectedTokens = test.param.expectedTokenPricesForAll } + require.NoError(t, err) - // we expect prices for at least all queried tokens (it is possible that additional tokens are returned). - assert.True(t, len(prices) >= len(test.param.expectedTokenPrices)) + // Ensure all expected prices are present. + assert.True(t, len(prices) == len(expectedTokens)) // Check prices are matching expected result. - for tk, expectedPrice := range test.param.expectedTokenPrices { + for tk, expectedPrice := range expectedTokens { if prices[cciptypes.Address(tk.String())] == nil { assert.Fail(t, "Token price not found") } @@ -113,25 +141,21 @@ func TestDynamicPriceGetter(t *testing.T) { } func testParamAggregatorOnly(t *testing.T) testParameters { - tk1 := utils.RandomAddress() - tk2 := utils.RandomAddress() - tk3 := utils.RandomAddress() - tk4 := utils.RandomAddress() cfg := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - tk1: { + TK1: { ChainID: 101, AggregatorContractAddress: utils.RandomAddress(), }, - tk2: { + TK2: { ChainID: 102, AggregatorContractAddress: utils.RandomAddress(), }, - tk3: { + TK3: { ChainID: 103, AggregatorContractAddress: utils.RandomAddress(), }, - tk4: { + TK4: { ChainID: 104, AggregatorContractAddress: utils.RandomAddress(), }, @@ -177,15 +201,15 @@ func testParamAggregatorOnly(t *testing.T) testParameters { uint64(104): mockClient(t, []uint8{20}, []aggregator_v3_interface.LatestRoundData{round4}), } expectedTokenPrices := map[common.Address]big.Int{ - tk1: *multExp(round1.Answer, 10), // expected in 1e18 format. - tk2: *multExp(round2.Answer, 10), // expected in 1e18 format. - tk3: *round3.Answer, // already in 1e18 format (contract decimals==18). - tk4: *multExp(big.NewInt(1234567890), 8), // expected in 1e18 format. + TK1: *multExp(round1.Answer, 10), // expected in 1e18 format. + TK2: *multExp(round2.Answer, 10), // expected in 1e18 format. + TK3: *round3.Answer, // already in 1e18 format (contract decimals==18). + TK4: *multExp(big.NewInt(1234567890), 8), // expected in 1e18 format. } return testParameters{ cfg: cfg, evmClients: evmClients, - tokens: []common.Address{tk1, tk2, tk3, tk4}, + tokens: []common.Address{TK1, TK2, TK3, TK4}, expectedTokenPrices: expectedTokenPrices, invalidConfigErrorExpected: false, } @@ -193,20 +217,17 @@ func testParamAggregatorOnly(t *testing.T) testParameters { // testParamAggregatorOnlyMulti test with several tokens on chain 102. func testParamAggregatorOnlyMulti(t *testing.T) testParameters { - tk1 := utils.RandomAddress() - tk2 := utils.RandomAddress() - tk3 := utils.RandomAddress() cfg := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - tk1: { + TK1: { ChainID: 101, AggregatorContractAddress: utils.RandomAddress(), }, - tk2: { + TK2: { ChainID: 102, AggregatorContractAddress: utils.RandomAddress(), }, - tk3: { + TK3: { ChainID: 102, AggregatorContractAddress: utils.RandomAddress(), }, @@ -241,35 +262,32 @@ func testParamAggregatorOnlyMulti(t *testing.T) testParameters { uint64(102): mockClient(t, []uint8{8, 8}, []aggregator_v3_interface.LatestRoundData{round2, round3}), } expectedTokenPrices := map[common.Address]big.Int{ - tk1: *multExp(round1.Answer, 10), - tk2: *multExp(round2.Answer, 10), - tk3: *multExp(round3.Answer, 10), + TK1: *multExp(round1.Answer, 10), + TK2: *multExp(round2.Answer, 10), + TK3: *multExp(round3.Answer, 10), } return testParameters{ cfg: cfg, evmClients: evmClients, invalidConfigErrorExpected: false, - tokens: []common.Address{tk1, tk2, tk3}, + tokens: []common.Address{TK1, TK2, TK3}, expectedTokenPrices: expectedTokenPrices, } } func testParamStaticOnly() testParameters { - tk1 := utils.RandomAddress() - tk2 := utils.RandomAddress() - tk3 := utils.RandomAddress() cfg := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{}, StaticPrices: map[common.Address]config.StaticPriceConfig{ - tk1: { + TK1: { ChainID: 101, Price: big.NewInt(1_234_000), }, - tk2: { + TK2: { ChainID: 102, Price: big.NewInt(2_234_000), }, - tk3: { + TK3: { ChainID: 103, Price: big.NewInt(3_234_000), }, @@ -278,35 +296,86 @@ func testParamStaticOnly() testParameters { // Real LINK/USD example from OP. evmClients := map[uint64]DynamicPriceGetterClient{} expectedTokenPrices := map[common.Address]big.Int{ - tk1: *cfg.StaticPrices[tk1].Price, - tk2: *cfg.StaticPrices[tk2].Price, - tk3: *cfg.StaticPrices[tk3].Price, + TK1: *cfg.StaticPrices[TK1].Price, + TK2: *cfg.StaticPrices[TK2].Price, + TK3: *cfg.StaticPrices[TK3].Price, } return testParameters{ cfg: cfg, evmClients: evmClients, - tokens: []common.Address{tk1, tk2, tk3}, + tokens: []common.Address{TK1, TK2, TK3}, expectedTokenPrices: expectedTokenPrices, } } +func testParamNoAggregatorForToken(t *testing.T) testParameters { + cfg := config.DynamicPriceGetterConfig{ + AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ + TK1: { + ChainID: 101, + AggregatorContractAddress: utils.RandomAddress(), + }, + TK2: { + ChainID: 102, + AggregatorContractAddress: utils.RandomAddress(), + }, + }, + StaticPrices: map[common.Address]config.StaticPriceConfig{ + TK3: { + ChainID: 103, + Price: big.NewInt(1_234_000), + }, + }, + } + // Real LINK/USD example from OP. + round1 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(1000), + Answer: big.NewInt(1396818990), + StartedAt: big.NewInt(1704896575), + UpdatedAt: big.NewInt(1704896575), + AnsweredInRound: big.NewInt(1000), + } + // Real ETH/USD example from OP. + round2 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(2000), + Answer: big.NewInt(238879815123), + StartedAt: big.NewInt(1704897197), + UpdatedAt: big.NewInt(1704897197), + AnsweredInRound: big.NewInt(2000), + } + evmClients := map[uint64]DynamicPriceGetterClient{ + uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), + } + expectedTokenPrices := map[common.Address]big.Int{ + TK1: *round1.Answer, + TK2: *round2.Answer, + TK3: *cfg.StaticPrices[TK3].Price, + TK4: *big.NewInt(0), + } + return testParameters{ + cfg: cfg, + evmClients: evmClients, + tokens: []common.Address{TK1, TK2, TK3, TK4}, + expectedTokenPrices: expectedTokenPrices, + priceResolutionErrorExpected: true, + } +} + func testParamAggregatorAndStaticValid(t *testing.T) testParameters { - tk1 := utils.RandomAddress() - tk2 := utils.RandomAddress() - tk3 := utils.RandomAddress() cfg := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - tk1: { + TK1: { ChainID: 101, AggregatorContractAddress: utils.RandomAddress(), }, - tk2: { + TK2: { ChainID: 102, AggregatorContractAddress: utils.RandomAddress(), }, }, StaticPrices: map[common.Address]config.StaticPriceConfig{ - tk3: { + TK3: { ChainID: 103, Price: big.NewInt(1_234_000), }, @@ -333,39 +402,36 @@ func testParamAggregatorAndStaticValid(t *testing.T) testParameters { uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), } expectedTokenPrices := map[common.Address]big.Int{ - tk1: *multExp(round1.Answer, 10), - tk2: *multExp(round2.Answer, 10), - tk3: *cfg.StaticPrices[tk3].Price, + TK1: *multExp(round1.Answer, 10), + TK2: *multExp(round2.Answer, 10), + TK3: *cfg.StaticPrices[TK3].Price, } return testParameters{ cfg: cfg, evmClients: evmClients, - tokens: []common.Address{tk1, tk2, tk3}, + tokens: []common.Address{TK1, TK2, TK3}, expectedTokenPrices: expectedTokenPrices, } } func testParamAggregatorAndStaticTokenCollision(t *testing.T) testParameters { - tk1 := utils.RandomAddress() - tk2 := utils.RandomAddress() - tk3 := utils.RandomAddress() cfg := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - tk1: { + TK1: { ChainID: 101, AggregatorContractAddress: utils.RandomAddress(), }, - tk2: { + TK2: { ChainID: 102, AggregatorContractAddress: utils.RandomAddress(), }, - tk3: { + TK3: { ChainID: 103, AggregatorContractAddress: utils.RandomAddress(), }, }, StaticPrices: map[common.Address]config.StaticPriceConfig{ - tk3: { + TK3: { ChainID: 103, Price: big.NewInt(1_234_000), }, @@ -402,29 +468,25 @@ func testParamAggregatorAndStaticTokenCollision(t *testing.T) testParameters { return testParameters{ cfg: cfg, evmClients: evmClients, - tokens: []common.Address{tk1, tk2, tk3}, + tokens: []common.Address{TK1, TK2, TK3}, invalidConfigErrorExpected: true, } } -func testParamNoAggregatorForToken(t *testing.T) testParameters { - tk1 := utils.RandomAddress() - tk2 := utils.RandomAddress() - tk3 := utils.RandomAddress() - tk4 := utils.RandomAddress() +func testParamBatchCallReturnsErr(t *testing.T) testParameters { cfg := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - tk1: { + TK1: { ChainID: 101, AggregatorContractAddress: utils.RandomAddress(), }, - tk2: { + TK2: { ChainID: 102, AggregatorContractAddress: utils.RandomAddress(), }, }, StaticPrices: map[common.Address]config.StaticPriceConfig{ - tk3: { + TK3: { ChainID: 103, Price: big.NewInt(1_234_000), }, @@ -438,6 +500,51 @@ func testParamNoAggregatorForToken(t *testing.T) testParameters { UpdatedAt: big.NewInt(1704896575), AnsweredInRound: big.NewInt(1000), } + evmClients := map[uint64]DynamicPriceGetterClient{ + uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): { + BatchCaller: mockErrCaller(t), + }, + } + return testParameters{ + cfg: cfg, + evmClients: evmClients, + tokens: []common.Address{TK1, TK2, TK3}, + evmCallErr: true, + } +} + +func testLessInputsThanDefinedPrices(t *testing.T) testParameters { + cfg := config.DynamicPriceGetterConfig{ + AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ + TK1: { + ChainID: 101, + AggregatorContractAddress: utils.RandomAddress(), + }, + TK2: { + ChainID: 102, + AggregatorContractAddress: utils.RandomAddress(), + }, + TK3: { + ChainID: 103, + AggregatorContractAddress: utils.RandomAddress(), + }, + }, + StaticPrices: map[common.Address]config.StaticPriceConfig{ + TK4: { + ChainID: 104, + Price: big.NewInt(1_234_000), + }, + }, + } + // Real LINK/USD example from OP. + round1 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(1000), + Answer: big.NewInt(3749350456), + StartedAt: big.NewInt(1704896575), + UpdatedAt: big.NewInt(1704896575), + AnsweredInRound: big.NewInt(1000), + } // Real ETH/USD example from OP. round2 := aggregator_v3_interface.LatestRoundData{ RoundId: big.NewInt(2000), @@ -446,43 +553,51 @@ func testParamNoAggregatorForToken(t *testing.T) testParameters { UpdatedAt: big.NewInt(1704897197), AnsweredInRound: big.NewInt(2000), } + // Real LINK/ETH example from OP. + round3 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(3000), + Answer: big.NewInt(4468862777874802), + StartedAt: big.NewInt(1715743907), + UpdatedAt: big.NewInt(1715743907), + AnsweredInRound: big.NewInt(3000), + } evmClients := map[uint64]DynamicPriceGetterClient{ uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(103): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}), } expectedTokenPrices := map[common.Address]big.Int{ - tk1: *round1.Answer, - tk2: *round2.Answer, - tk3: *cfg.StaticPrices[tk3].Price, - tk4: *big.NewInt(0), + TK1: *multExp(round1.Answer, 10), + TK2: *multExp(round2.Answer, 10), + TK3: *multExp(round3.Answer, 10), } return testParameters{ - cfg: cfg, - evmClients: evmClients, - tokens: []common.Address{tk1, tk2, tk3, tk4}, - expectedTokenPrices: expectedTokenPrices, - priceResolutionErrorExpected: true, + cfg: cfg, + evmClients: evmClients, + tokens: []common.Address{TK1, TK2, TK3}, + expectedTokenPrices: expectedTokenPrices, } } -func testParamBatchCallReturnsErr(t *testing.T) testParameters { - tk1 := utils.RandomAddress() - tk2 := utils.RandomAddress() - tk3 := utils.RandomAddress() +func testGetAllTokensAggregatorAndStatic(t *testing.T) testParameters { cfg := config.DynamicPriceGetterConfig{ AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ - tk1: { + TK1: { ChainID: 101, AggregatorContractAddress: utils.RandomAddress(), }, - tk2: { + TK2: { ChainID: 102, AggregatorContractAddress: utils.RandomAddress(), }, + TK3: { + ChainID: 103, + AggregatorContractAddress: utils.RandomAddress(), + }, }, StaticPrices: map[common.Address]config.StaticPriceConfig{ - tk3: { - ChainID: 103, + TK4: { + ChainID: 104, Price: big.NewInt(1_234_000), }, }, @@ -490,22 +605,133 @@ func testParamBatchCallReturnsErr(t *testing.T) testParameters { // Real LINK/USD example from OP. round1 := aggregator_v3_interface.LatestRoundData{ RoundId: big.NewInt(1000), - Answer: big.NewInt(1396818990), + Answer: big.NewInt(3749350456), StartedAt: big.NewInt(1704896575), UpdatedAt: big.NewInt(1704896575), AnsweredInRound: big.NewInt(1000), } + // Real ETH/USD example from OP. + round2 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(2000), + Answer: big.NewInt(238879815123), + StartedAt: big.NewInt(1704897197), + UpdatedAt: big.NewInt(1704897197), + AnsweredInRound: big.NewInt(2000), + } + // Real LINK/ETH example from OP. + round3 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(3000), + Answer: big.NewInt(4468862777874802), + StartedAt: big.NewInt(1715743907), + UpdatedAt: big.NewInt(1715743907), + AnsweredInRound: big.NewInt(3000), + } evmClients := map[uint64]DynamicPriceGetterClient{ uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), - uint64(102): { - BatchCaller: mockErrCaller(t), + uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(103): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}), + } + expectedTokenPricesForAll := map[common.Address]big.Int{ + TK1: *multExp(round1.Answer, 10), + TK2: *multExp(round2.Answer, 10), + TK3: *multExp(round3.Answer, 10), + TK4: *cfg.StaticPrices[TK4].Price, + } + return testParameters{ + cfg: cfg, + evmClients: evmClients, + expectedTokenPricesForAll: expectedTokenPricesForAll, + } +} + +func testGetAllTokensAggregatorOnly(t *testing.T) testParameters { + cfg := config.DynamicPriceGetterConfig{ + AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{ + TK1: { + ChainID: 101, + AggregatorContractAddress: utils.RandomAddress(), + }, + TK2: { + ChainID: 102, + AggregatorContractAddress: utils.RandomAddress(), + }, + TK3: { + ChainID: 103, + AggregatorContractAddress: utils.RandomAddress(), + }, }, + StaticPrices: map[common.Address]config.StaticPriceConfig{}, + } + // Real LINK/USD example from OP. + round1 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(1000), + Answer: big.NewInt(3749350456), + StartedAt: big.NewInt(1704896575), + UpdatedAt: big.NewInt(1704896575), + AnsweredInRound: big.NewInt(1000), + } + // Real ETH/USD example from OP. + round2 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(2000), + Answer: big.NewInt(238879815123), + StartedAt: big.NewInt(1704897197), + UpdatedAt: big.NewInt(1704897197), + AnsweredInRound: big.NewInt(2000), + } + // Real LINK/ETH example from OP. + round3 := aggregator_v3_interface.LatestRoundData{ + RoundId: big.NewInt(3000), + Answer: big.NewInt(4468862777874802), + StartedAt: big.NewInt(1715743907), + UpdatedAt: big.NewInt(1715743907), + AnsweredInRound: big.NewInt(3000), + } + evmClients := map[uint64]DynamicPriceGetterClient{ + uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}), + uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}), + uint64(103): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}), + } + expectedTokenPricesForAll := map[common.Address]big.Int{ + TK1: *multExp(round1.Answer, 10), + TK2: *multExp(round2.Answer, 10), + TK3: *multExp(round3.Answer, 10), } return testParameters{ - cfg: cfg, - evmClients: evmClients, - tokens: []common.Address{tk1, tk2, tk3}, - evmCallErr: true, + cfg: cfg, + evmClients: evmClients, + expectedTokenPricesForAll: expectedTokenPricesForAll, + } +} + +func testGetAllTokensStaticOnly() testParameters { + cfg := config.DynamicPriceGetterConfig{ + AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{}, + StaticPrices: map[common.Address]config.StaticPriceConfig{ + TK1: { + ChainID: 101, + Price: big.NewInt(1_234_000), + }, + TK2: { + ChainID: 102, + Price: big.NewInt(2_234_000), + }, + TK3: { + ChainID: 103, + Price: big.NewInt(3_234_000), + }, + }, + } + + evmClients := map[uint64]DynamicPriceGetterClient{} + expectedTokenPricesForAll := map[common.Address]big.Int{ + TK1: *cfg.StaticPrices[TK1].Price, + TK2: *cfg.StaticPrices[TK2].Price, + TK3: *cfg.StaticPrices[TK3].Price, + } + return testParameters{ + cfg: cfg, + evmClients: evmClients, + expectedTokenPricesForAll: expectedTokenPricesForAll, } } diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go index ae9a10deb65..34977eda9f1 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" - "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/parseutil" @@ -60,28 +59,30 @@ func (d *PipelineGetter) FilterConfiguredTokens(ctx context.Context, tokens []cc return configured, unconfigured, nil } -func (d *PipelineGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.Address) (map[cciptypes.Address]*big.Int, error) { - _, trrs, err := d.runner.ExecuteRun(ctx, pipeline.Spec{ - ID: d.jobID, - DotDagSource: d.source, - CreatedAt: time.Now(), - JobID: d.jobID, - JobName: d.name, - JobType: "", - }, pipeline.NewVarsFrom(map[string]interface{}{})) +func (d *PipelineGetter) GetJobSpecTokenPricesUSD(ctx context.Context) (map[cciptypes.Address]*big.Int, error) { + prices, err := d.getPricesFromRunner(ctx) if err != nil { return nil, err } - finalResult := trrs.FinalResult() - if finalResult.HasErrors() { - return nil, errors.Errorf("error getting prices %v", finalResult.AllErrors) - } - if len(finalResult.Values) != 1 { - return nil, errors.Errorf("invalid number of price results, expected 1 got %v", len(finalResult.Values)) + + tokenPrices := make(map[cciptypes.Address]*big.Int) + for tokenAddressStr, rawPrice := range prices { + tokenAddressStr := ccipcalc.HexToAddress(tokenAddressStr) + castedPrice, err := parseutil.ParseBigIntFromAny(rawPrice) + if err != nil { + return nil, err + } + + tokenPrices[tokenAddressStr] = castedPrice } - prices, ok := finalResult.Values[0].(map[string]interface{}) - if !ok { - return nil, errors.Errorf("expected map output of price pipeline, got %T", finalResult.Values[0]) + + return tokenPrices, nil +} + +func (d *PipelineGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.Address) (map[cciptypes.Address]*big.Int, error) { + prices, err := d.getPricesFromRunner(ctx) + if err != nil { + return nil, err } providedTokensSet := mapset.NewSet(tokens...) @@ -101,7 +102,7 @@ func (d *PipelineGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes. // The mapping of token address to source of token price has to live offchain. // Best we can do is sanity check that the token price spec covers all our desired execution token prices. for _, token := range tokens { - if _, ok = tokenPrices[token]; !ok { + if _, ok := tokenPrices[token]; !ok { return nil, errors.Errorf("missing token %s from tokensForFeeCoin spec, got %v", token, prices) } } @@ -109,6 +110,33 @@ func (d *PipelineGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes. return tokenPrices, nil } +func (d *PipelineGetter) getPricesFromRunner(ctx context.Context) (map[string]interface{}, error) { + _, trrs, err := d.runner.ExecuteRun(ctx, pipeline.Spec{ + ID: d.jobID, + DotDagSource: d.source, + CreatedAt: time.Now(), + JobID: d.jobID, + JobName: d.name, + JobType: "", + }, pipeline.NewVarsFrom(map[string]interface{}{})) + if err != nil { + return nil, err + } + finalResult := trrs.FinalResult() + if finalResult.HasErrors() { + return nil, errors.Errorf("error getting prices %v", finalResult.AllErrors) + } + if len(finalResult.Values) != 1 { + return nil, errors.Errorf("invalid number of price results, expected 1 got %v", len(finalResult.Values)) + } + prices, ok := finalResult.Values[0].(map[string]interface{}) + if !ok { + return nil, errors.Errorf("expected map output of price pipeline, got %T", finalResult.Values[0]) + } + + return prices, nil +} + func (d *PipelineGetter) Close() error { return d.runner.Close() } diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go index 37970750732..8aeeff96b57 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go @@ -57,19 +57,21 @@ func TestDataSource(t *testing.T) { priceGetter := newTestPipelineGetter(t, source) - // USDC & LINK are configured - confTokens, _, err := priceGetter.FilterConfiguredTokens(context.Background(), []cciptypes.Address{linkTokenAddress, usdcTokenAddress}) + // Ask for all prices present in spec. + prices, err := priceGetter.GetJobSpecTokenPricesUSD(context.Background()) require.NoError(t, err) - assert.Equal(t, linkTokenAddress, confTokens[0]) - assert.Equal(t, usdcTokenAddress, confTokens[1]) + assert.Equal(t, prices, map[cciptypes.Address]*big.Int{ + linkTokenAddress: big.NewInt(0).Mul(big.NewInt(200), big.NewInt(1000000000000000000)), + usdcTokenAddress: big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(1000000000000000000)), + }) - // Ask for all prices present in spec. - prices, err := priceGetter.TokenPricesUSD(context.Background(), []cciptypes.Address{ + // Specifically ask for all prices + pricesWithInput, errWithInput := priceGetter.TokenPricesUSD(context.Background(), []cciptypes.Address{ linkTokenAddress, usdcTokenAddress, }) - require.NoError(t, err) - assert.Equal(t, prices, map[cciptypes.Address]*big.Int{ + require.NoError(t, errWithInput) + assert.Equal(t, pricesWithInput, map[cciptypes.Address]*big.Int{ linkTokenAddress: big.NewInt(0).Mul(big.NewInt(200), big.NewInt(1000000000000000000)), usdcTokenAddress: big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(1000000000000000000)), }) diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/pricegetter.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/pricegetter.go index 9ee0e8f3d0a..d4bcfc57b6e 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/pricegetter.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/pricegetter.go @@ -1,7 +1,18 @@ package pricegetter -import cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" +import ( + "context" + "math/big" + + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" +) type PriceGetter interface { cciptypes.PriceGetter } + +type AllTokensPriceGetter interface { + PriceGetter + // GetJobSpecTokenPricesUSD returns all token prices defined in the jobspec. + GetJobSpecTokenPricesUSD(ctx context.Context) (map[cciptypes.Address]*big.Int, error) +} diff --git a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go index 177ccf323b7..fe9021e4c14 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go @@ -7,6 +7,7 @@ import ( "math/big" "net/http" "net/http/httptest" + "slices" "strconv" "strings" "testing" @@ -49,6 +50,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/smartcontractkit/chainlink/v2/core/logger/audit" @@ -762,6 +764,47 @@ func (c *CCIPIntegrationTestHarness) NoNodesHaveExecutedSeqNum(t *testing.T, seq return log } +func (c *CCIPIntegrationTestHarness) EventuallyPriceRegistryUpdated(t *testing.T, block uint64, srcSelector uint64, tokens []common.Address, sourceNative common.Address, priceRegistryOpts ...common.Address) { + var priceRegistry *price_registry_1_2_0.PriceRegistry + var err error + if len(priceRegistryOpts) > 0 { + priceRegistry, err = price_registry_1_2_0.NewPriceRegistry(priceRegistryOpts[0], c.Dest.Chain) + require.NoError(t, err) + } else { + require.NotNil(t, c.Dest.PriceRegistry, "no priceRegistry configured") + priceRegistry = c.Dest.PriceRegistry + } + + g := gomega.NewGomegaWithT(t) + g.Eventually(func() bool { + it, err := priceRegistry.FilterUsdPerTokenUpdated(&bind.FilterOpts{Start: block}, tokens) + g.Expect(err).NotTo(gomega.HaveOccurred(), "Error filtering UsdPerTokenUpdated event") + + tokensFetched := make([]common.Address, 0, len(tokens)) + for it.Next() { + tokenFetched := it.Event.Token + tokensFetched = append(tokensFetched, tokenFetched) + t.Log("Token price updated", tokenFetched.String(), it.Event.Value.String(), it.Event.Timestamp.String()) + } + + for _, token := range tokens { + if !slices.Contains(tokensFetched, token) { + return false + } + } + + return true + }, testutils.WaitTimeout(t), 10*time.Second).Should(gomega.BeTrue(), "Tokens prices has not been updated") + + g.Eventually(func() bool { + it, err := priceRegistry.FilterUsdPerUnitGasUpdated(&bind.FilterOpts{Start: block}, []uint64{srcSelector}) + g.Expect(err).NotTo(gomega.HaveOccurred(), "Error filtering UsdPerUnitGasUpdated event") + g.Expect(it.Next()).To(gomega.BeTrue(), "No UsdPerUnitGasUpdated event found") + + return true + }, testutils.WaitTimeout(t), 10*time.Second).Should(gomega.BeTrue(), "source gas price has not been updated") +} + func (c *CCIPIntegrationTestHarness) EventuallyCommitReportAccepted(t *testing.T, currentBlock uint64, commitStoreOpts ...common.Address) commit_store.CommitStoreCommitReport { var commitStore *commit_store.CommitStore var err error diff --git a/core/store/migrate/migrations/0250_ccip_token_prices_fix.sql b/core/store/migrate/migrations/0250_ccip_token_prices_fix.sql new file mode 100644 index 00000000000..6c6cf02b43f --- /dev/null +++ b/core/store/migrate/migrations/0250_ccip_token_prices_fix.sql @@ -0,0 +1,49 @@ +-- +goose Up + +-- We need to re-create tables from scratch because of the unique constraint on tokens and chains selectors +DROP TABLE ccip.observed_token_prices; +DROP TABLE ccip.observed_gas_prices; + +CREATE TABLE ccip.observed_token_prices +( + chain_selector NUMERIC(20, 0) NOT NULL, + token_addr BYTEA NOT NULL, + token_price NUMERIC(78, 0) NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (chain_selector, token_addr) +); + +CREATE TABLE ccip.observed_gas_prices +( + chain_selector NUMERIC(20, 0) NOT NULL, + source_chain_selector NUMERIC(20, 0) NOT NULL, + gas_price NUMERIC(78, 0) NOT NULL, + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (chain_selector, source_chain_selector) +); + +-- +goose Down +DROP TABLE ccip.observed_token_prices; +DROP TABLE ccip.observed_gas_prices; + +-- Restore state from migration 0236_ccip_prices_cache.sql +CREATE TABLE ccip.observed_gas_prices +( + chain_selector NUMERIC(20, 0) NOT NULL, + job_id INTEGER NOT NULL, + source_chain_selector NUMERIC(20, 0) NOT NULL, + gas_price NUMERIC(78, 0) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE ccip.observed_token_prices +( + chain_selector NUMERIC(20, 0) NOT NULL, + job_id INTEGER NOT NULL, + token_addr BYTEA NOT NULL, + token_price NUMERIC(78, 0) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_ccip_gas_prices_chain_gas_price_timestamp ON ccip.observed_gas_prices (chain_selector, source_chain_selector, created_at DESC); +CREATE INDEX idx_ccip_token_prices_token_price_timestamp ON ccip.observed_token_prices (chain_selector, token_addr, created_at DESC); diff --git a/go.mod b/go.mod index 7b5de72594d..39517d43409 100644 --- a/go.mod +++ b/go.mod @@ -75,11 +75,11 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.21 github.com/smartcontractkit/chainlink-automation v1.0.4 github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097 github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564 + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 @@ -102,15 +102,15 @@ require ( go.opentelemetry.io/otel v1.28.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.25.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/mod v0.19.0 - golang.org/x/net v0.27.0 - golang.org/x/sync v0.7.0 - golang.org/x/term v0.22.0 - golang.org/x/text v0.16.0 + golang.org/x/crypto v0.26.0 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/mod v0.20.0 + golang.org/x/net v0.28.0 + golang.org/x/sync v0.8.0 + golang.org/x/term v0.23.0 + golang.org/x/text v0.17.0 golang.org/x/time v0.5.0 - golang.org/x/tools v0.23.0 + golang.org/x/tools v0.24.0 gonum.org/v1/gonum v0.15.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 @@ -336,7 +336,7 @@ require ( go.uber.org/ratelimit v0.3.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/sys v0.23.0 // indirect google.golang.org/api v0.188.0 // indirect google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect diff --git a/go.sum b/go.sum index 6574c9c0d80..c6ade4ce55a 100644 --- a/go.sum +++ b/go.sum @@ -1141,16 +1141,16 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95 h1:LAgJTg9Yr/uCo2g7Krp88Dco2U45Y6sbJVl8uKoLkys= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95/go.mod h1:/ZWraCBaDDgaIN1prixYcbVvIk/6HeED9+8zbWQ+TMo= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33 h1:bNsry/El6yigB00BBU3iIOhZHiPm5MgCrDkJEFNXL1U= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097 h1:88WOOrXy7t8IxS+91AKItN/HTzIHvEgFNetZDOhosKc= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097/go.mod h1:HqdnbHS9j9EKPidpSUI9S37zl0blbVjB+NP4ztdYta8= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f h1:I9fTBJpHkeldFplXUy71eLIn6A6GxuR4xrABoUeD+CM= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f/go.mod h1:V/86loaFSH0dqqUEHqyXVbyNqDRSjvcf9BRomWFTljU= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564 h1:8ZzsGNhqYxmQ/QMO1fuXO7u9Vpl9YUvPJK+td/ZaBJA= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564/go.mod h1:Ml88TJTwZCj6yHDkAEN/EhxVutzSlk+kDZgfibRIqF0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f h1:b0Ifwk7eK3fwJ0R69Ovhv5XvZ1/TUAfHkU5Jp7wbNZ0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f/go.mod h1:Ml88TJTwZCj6yHDkAEN/EhxVutzSlk+kDZgfibRIqF0= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799/go.mod h1:UVFRacRkP7O7TQAzFmR52v5mUlxf+G1ovMlCQAB/cHU= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 h1:TFe+FvzxClblt6qRfqEhUfa4kFQx5UobuoFGO2W4mMo= @@ -1426,8 +1426,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1438,8 +1438,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1467,8 +1467,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1524,8 +1524,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1550,8 +1550,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1636,8 +1636,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1646,8 +1646,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1661,8 +1661,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1734,8 +1734,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/integration-tests/actions/actions.go b/integration-tests/actions/actions.go index 8487e3a264e..b274b59416b 100644 --- a/integration-tests/actions/actions.go +++ b/integration-tests/actions/actions.go @@ -27,6 +27,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/utils/conversions" "github.com/smartcontractkit/chainlink/integration-tests/contracts" ethContracts "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" + "github.com/smartcontractkit/chainlink/integration-tests/wrappers" "github.com/ethereum/go-ethereum/accounts/abi" @@ -1024,18 +1025,42 @@ func SendLinkFundsToDeploymentAddresses( toTransferToMultiCallContract := big.NewInt(0).Mul(linkAmountPerUpkeep, big.NewInt(int64(totalUpkeeps+concurrency))) toTransferPerClient := big.NewInt(0).Mul(linkAmountPerUpkeep, big.NewInt(int64(operationsPerAddress+1))) - err := linkToken.Transfer(multicallAddress.Hex(), toTransferToMultiCallContract) + + // As a hack we use the geth wrapper directly, because we need to access receipt to get block number, which we will use to query the balance + // This is needed as querying with 'latest' block number very rarely, but still, return stale balance. That's happening even though we wait for + // the transaction to be mined. + linkInstance, err := link_token_interface.NewLinkToken(common.HexToAddress(linkToken.Address()), wrappers.MustNewWrappedContractBackend(nil, chainClient)) if err != nil { - return errors.Wrapf(err, "Error transferring LINK to multicall contract") + return err } - balance, err := linkToken.BalanceOf(context.Background(), multicallAddress.Hex()) + tx, err := chainClient.Decode(linkInstance.Transfer(chainClient.NewTXOpts(), multicallAddress, toTransferToMultiCallContract)) + if err != nil { + return err + } + + if tx.Receipt == nil { + return fmt.Errorf("transaction receipt for LINK transfer to multicall contract is nil") + } + + multiBalance, err := linkInstance.BalanceOf(&bind.CallOpts{From: chainClient.Addresses[0], BlockNumber: tx.Receipt.BlockNumber}, multicallAddress) if err != nil { return errors.Wrapf(err, "Error getting LINK balance of multicall contract") } - if balance.Cmp(toTransferToMultiCallContract) < 0 { - return fmt.Errorf("Incorrect LINK balance of multicall contract. Expected at least: %s. Got: %s", toTransferToMultiCallContract.String(), balance.String()) + // Old code that's querying latest block + //err := linkToken.Transfer(multicallAddress.Hex(), toTransferToMultiCallContract) + //if err != nil { + // return errors.Wrapf(err, "Error transferring LINK to multicall contract") + //} + // + //balance, err := linkToken.BalanceOf(context.Background(), multicallAddress.Hex()) + //if err != nil { + // return errors.Wrapf(err, "Error getting LINK balance of multicall contract") + //} + + if multiBalance.Cmp(toTransferToMultiCallContract) < 0 { + return fmt.Errorf("Incorrect LINK balance of multicall contract. Expected at least: %s. Got: %s", toTransferToMultiCallContract.String(), multiBalance.String()) } // Transfer LINK to ephemeral keys @@ -1060,18 +1085,24 @@ func SendLinkFundsToDeploymentAddresses( } boundContract := bind.NewBoundContract(multicallAddress, multiCallABI, chainClient.Client, chainClient.Client, chainClient.Client) // call aggregate3 to group all msg call data and send them in a single transaction - _, err = chainClient.Decode(boundContract.Transact(chainClient.NewTXOpts(), "aggregate3", call)) + ephemeralTx, err := chainClient.Decode(boundContract.Transact(chainClient.NewTXOpts(), "aggregate3", call)) if err != nil { return errors.Wrapf(err, "Error calling Multicall contract") } + if ephemeralTx.Receipt == nil { + return fmt.Errorf("transaction receipt for LINK transfer to ephemeral keys is nil") + } + for i := 1; i <= concurrency; i++ { - balance, err := linkToken.BalanceOf(context.Background(), chainClient.Addresses[i].Hex()) + ephemeralBalance, err := linkInstance.BalanceOf(&bind.CallOpts{From: chainClient.Addresses[0], BlockNumber: ephemeralTx.Receipt.BlockNumber}, chainClient.Addresses[i]) + // Old code that's querying latest block, for now we prefer to use block number from the transaction receipt + //balance, err := linkToken.BalanceOf(context.Background(), chainClient.Addresses[i].Hex()) if err != nil { return errors.Wrapf(err, "Error getting LINK balance of ephemeral key %d", i) } - if balance.Cmp(toTransferPerClient) < 0 { - return fmt.Errorf("Incorrect LINK balance after transfer. Ephemeral key %d. Expected: %s. Got: %s", i, toTransferPerClient.String(), balance.String()) + if ephemeralBalance.Cmp(toTransferPerClient) < 0 { + return fmt.Errorf("Incorrect LINK balance after transfer. Ephemeral key %d. Expected: %s. Got: %s", i, toTransferPerClient.String(), ephemeralBalance.String()) } } diff --git a/integration-tests/crib/chaos.go b/integration-tests/crib/chaos.go index bbf6ca681cd..463ced483fa 100644 --- a/integration-tests/crib/chaos.go +++ b/integration-tests/crib/chaos.go @@ -4,16 +4,17 @@ import ( "time" "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" - "github.com/smartcontractkit/havoc/k8schaos" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/smartcontractkit/chainlink-testing-framework/havoc" ) -func rebootCLNamespace(delay time.Duration, namespace string) (*k8schaos.Chaos, error) { - k8sClient, err := k8schaos.NewChaosMeshClient() +func rebootCLNamespace(delay time.Duration, namespace string) (*havoc.Chaos, error) { + k8sClient, err := havoc.NewChaosMeshClient() if err != nil { return nil, err } - return k8schaos.NewChaos(k8schaos.ChaosOpts{ + return havoc.NewChaos(havoc.ChaosOpts{ Description: "Reboot CRIB", DelayCreate: delay, Object: &v1alpha1.PodChaos{ @@ -40,6 +41,6 @@ func rebootCLNamespace(delay time.Duration, namespace string) (*k8schaos.Chaos, }, }, Client: k8sClient, - Logger: &k8schaos.Logger, + Logger: &havoc.Logger, }) } diff --git a/integration-tests/crib/ocr_test.go b/integration-tests/crib/ocr_test.go index 91a7a1d76b8..0b7c38597cc 100644 --- a/integration-tests/crib/ocr_test.go +++ b/integration-tests/crib/ocr_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/smartcontractkit/havoc/k8schaos" + "github.com/smartcontractkit/chainlink-testing-framework/havoc" "github.com/stretchr/testify/require" @@ -40,7 +40,7 @@ func TestCRIB(t *testing.T) { os.Getenv("CRIB_NAMESPACE"), ) ch.Create(context.Background()) - ch.AddListener(k8schaos.NewChaosLogger(l)) + ch.AddListener(havoc.NewChaosLogger(l)) t.Cleanup(func() { err := ch.Delete(context.Background()) require.NoError(t, err) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index b4c1839c74a..eeb91617a4b 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -11,10 +11,10 @@ require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/avast/retry-go/v4 v4.6.0 github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df - github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240709130330-9f4feec7553f + github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240821051457-da69c6d9617a github.com/cli/go-gh/v2 v2.0.0 github.com/ethereum/go-ethereum v1.13.8 - github.com/fxamacker/cbor/v2 v2.6.0 + github.com/fxamacker/cbor/v2 v2.7.0 github.com/go-resty/resty/v2 v2.11.0 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 @@ -26,22 +26,22 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 github.com/pkg/errors v0.9.1 github.com/prometheus/common v0.55.0 - github.com/rs/zerolog v1.31.0 + github.com/rs/zerolog v1.33.0 github.com/scylladb/go-reflectx v1.0.1 github.com/segmentio/ksuid v1.0.4 github.com/shopspring/decimal v1.4.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chain-selectors v1.0.21 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33 - github.com/smartcontractkit/chainlink-testing-framework v1.34.5 - github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240405215812-5a72bc9af239 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097 + github.com/smartcontractkit/chainlink-testing-framework v1.34.6 + github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.1 + github.com/smartcontractkit/chainlink-testing-framework/havoc v0.0.0-20240822140612-df8e03c10dc1 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 - github.com/smartcontractkit/havoc/k8schaos v0.0.0-20240409145249-e78d20847e37 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 github.com/smartcontractkit/seth v1.1.2 github.com/smartcontractkit/wasp v0.4.5 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/test-go/testify v1.1.4 github.com/testcontainers/testcontainers-go v0.28.0 @@ -49,12 +49,12 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.25.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/sync v0.7.0 - golang.org/x/text v0.16.0 + golang.org/x/crypto v0.26.0 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/sync v0.8.0 + golang.org/x/text v0.17.0 gopkg.in/guregu/null.v4 v4.0.0 - k8s.io/apimachinery v0.28.2 + k8s.io/apimachinery v0.31.0 ) // avoids ambigious imports of indirect dependencies @@ -97,13 +97,28 @@ require ( github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect github.com/aws/aws-sdk-go v1.45.25 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.28 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.28 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 // indirect github.com/aws/constructs-go/constructs/v10 v10.1.255 // indirect github.com/aws/jsii-runtime-go v1.75.0 // indirect + github.com/aws/smithy-go v1.20.4 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/blendle/zapdriver v1.3.1 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -168,15 +183,13 @@ require ( github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/esote/minmaxheap v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fvbommel/sortorder v1.1.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gagliardetto/binary v0.7.7 // indirect github.com/gagliardetto/solana-go v1.8.4 // indirect @@ -229,7 +242,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/context v1.1.1 // indirect @@ -343,6 +356,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect @@ -385,7 +399,7 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect @@ -428,10 +442,10 @@ require ( github.com/zondax/ledger-go v0.14.1 // indirect go.dedis.ch/fixbuf v1.0.3 // indirect go.dedis.ch/kyber/v3 v3.1.0 // indirect - go.etcd.io/bbolt v1.3.8 // indirect - go.etcd.io/etcd/api/v3 v3.5.10 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect - go.etcd.io/etcd/client/v3 v3.5.10 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.etcd.io/etcd/api/v3 v3.5.14 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect + go.etcd.io/etcd/client/v3 v3.5.14 // indirect go.mongodb.org/mongo-driver v1.15.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/collector/pdata v1.0.0-rcv0016 // indirect @@ -450,13 +464,13 @@ require ( go.uber.org/ratelimit v0.3.0 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/term v0.23.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/tools v0.24.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gonum.org/v1/gonum v0.15.0 // indirect google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect @@ -464,27 +478,28 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.28.2 // indirect - k8s.io/apiextensions-apiserver v0.28.2 // indirect - k8s.io/cli-runtime v0.28.2 // indirect - k8s.io/client-go v0.28.2 // indirect - k8s.io/component-base v0.28.2 // indirect + k8s.io/api v0.31.0 // indirect + k8s.io/apiextensions-apiserver v0.31.0 // indirect + k8s.io/cli-runtime v0.31.0 // indirect + k8s.io/client-go v0.31.0 // indirect + k8s.io/component-base v0.31.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f // indirect - k8s.io/kubectl v0.28.2 // indirect - k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect + k8s.io/kubectl v0.31.0 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect nhooyr.io/websocket v1.8.7 // indirect pgregory.net/rapid v0.5.5 // indirect rsc.io/tmplfunc v0.0.3 // indirect - sigs.k8s.io/controller-runtime v0.16.2 // indirect + sigs.k8s.io/controller-runtime v0.19.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect - sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/kustomize/api v0.17.2 // indirect + sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 53c0c5645f1..fc2836ac78f 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -216,10 +216,38 @@ github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= +github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/config v1.27.28 h1:OTxWGW/91C61QlneCtnD62NLb4W616/NM1jA8LhJqbg= +github.com/aws/aws-sdk-go-v2/config v1.27.28/go.mod h1:uzVRVtJSU5EFv6Fu82AoVFKozJi2ZCY6WRCXj06rbvs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.28 h1:m8+AHY/ND8CMHJnPoH7PJIRakWGa4gbfbxuY9TGTUXM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.28/go.mod h1:6TF7dSc78ehD1SL6KpRIPKMA1GyyWflIkjqg+qmf4+c= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.5 h1:UDXu9dqpCZYonj7poM4kFISjzTdWI0v3WUusM+w+Gfc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.5/go.mod h1:5NPkI3RsTOhwz1CuG7VVSgJCm3CINKkoIaUbUZWQ67w= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 h1:iAckBT2OeEK/kBDyN/jDtpEExhjeeA/Im2q4X0rJZT8= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.4/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= github.com/aws/constructs-go/constructs/v10 v10.1.255 h1:5hARfEmhBqHSTQf/C3QLA3sWOxO2Dfja0iA1W7ZcI7g= github.com/aws/constructs-go/constructs/v10 v10.1.255/go.mod h1:DCdBSjN04Ck2pajCacTD4RKFqSA7Utya8d62XreYctI= github.com/aws/jsii-runtime-go v1.75.0 h1:NhpUfyiL7/wsRuUekFsz8FFBCYLfPD/l61kKg9kL/a4= github.com/aws/jsii-runtime-go v1.75.0/go.mod h1:TKCyrtM0pygEPo4rDZzbMSDNCDNTSYSN6/mGyHI6O3I= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= @@ -238,6 +266,8 @@ github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsy github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= @@ -279,8 +309,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240709130330-9f4feec7553f h1:onZ3oc6l1Gz8pVpQ0c1U1Cb11kIMoDb3xtEy/iZbYZM= -github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240709130330-9f4feec7553f/go.mod h1:x11iCbZV6hzzSQWMq610B6Wl5Lg1dhwqcVfeiWQQnQQ= +github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240821051457-da69c6d9617a h1:6Pg3a6j/41QDzH/oYcMLwwKsf3x/HXcu9W/dBaf2Hzs= +github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240821051457-da69c6d9617a/go.mod h1:x11iCbZV6hzzSQWMq610B6Wl5Lg1dhwqcVfeiWQQnQQ= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -390,8 +420,8 @@ github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= @@ -518,12 +548,10 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= -github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gagliardetto/binary v0.7.7 h1:QZpT38+sgoPg+TIQjH94sLbl/vX+nlIRA37pEyOsjfY= @@ -587,8 +615,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -682,15 +710,12 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= -github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= -github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= -github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -807,8 +832,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= @@ -1152,6 +1177,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/linode/linodego v1.19.0 h1:n4WJrcr9+30e9JGZ6DI0nZbm5SdAj1kSwvvt/998YUw= github.com/linode/linodego v1.19.0/go.mod h1:XZFR+yJ9mm2kwf6itZ6SCpu+6w3KnIevV0Uu5HNWJgQ= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -1289,6 +1316,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -1313,8 +1342,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= -github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= @@ -1434,8 +1463,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -1490,28 +1519,28 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95 h1:LAgJTg9Yr/uCo2g7Krp88Dco2U45Y6sbJVl8uKoLkys= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95/go.mod h1:/ZWraCBaDDgaIN1prixYcbVvIk/6HeED9+8zbWQ+TMo= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33 h1:bNsry/El6yigB00BBU3iIOhZHiPm5MgCrDkJEFNXL1U= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097 h1:88WOOrXy7t8IxS+91AKItN/HTzIHvEgFNetZDOhosKc= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097/go.mod h1:HqdnbHS9j9EKPidpSUI9S37zl0blbVjB+NP4ztdYta8= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f h1:I9fTBJpHkeldFplXUy71eLIn6A6GxuR4xrABoUeD+CM= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f/go.mod h1:V/86loaFSH0dqqUEHqyXVbyNqDRSjvcf9BRomWFTljU= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564 h1:8ZzsGNhqYxmQ/QMO1fuXO7u9Vpl9YUvPJK+td/ZaBJA= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564/go.mod h1:Ml88TJTwZCj6yHDkAEN/EhxVutzSlk+kDZgfibRIqF0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f h1:b0Ifwk7eK3fwJ0R69Ovhv5XvZ1/TUAfHkU5Jp7wbNZ0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f/go.mod h1:Ml88TJTwZCj6yHDkAEN/EhxVutzSlk+kDZgfibRIqF0= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799/go.mod h1:UVFRacRkP7O7TQAzFmR52v5mUlxf+G1ovMlCQAB/cHU= -github.com/smartcontractkit/chainlink-testing-framework v1.34.5 h1:6itLSDW4NHDDNR+Y+Z8YDzxxCN9SjdKb6SmLCl0vTFM= -github.com/smartcontractkit/chainlink-testing-framework v1.34.5/go.mod h1:hRZEDh2+afO8MSZb9qYNscmWb+3mHZf01J5ACZuIdTQ= -github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240405215812-5a72bc9af239 h1:Kk5OVlx/5g9q3Z3lhxytZS4/f8ds1MiNM8yaHgK3Oe8= -github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240405215812-5a72bc9af239/go.mod h1:DC8sQMyTlI/44UCTL8QWFwb0bYNoXCfjwCv2hMivYZU= +github.com/smartcontractkit/chainlink-testing-framework v1.34.6 h1:x6pdcJ1hcdRJEQxyQltSMbLs+g5ddRw1HOLuLOAnSaI= +github.com/smartcontractkit/chainlink-testing-framework v1.34.6/go.mod h1:tweBFcNZQpKfRtXDQBrvVRrnAmGclByiyuyF/qAU/Eo= +github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.1 h1:1/r1wQZ4TOFpZ13w94r7amdF096Z96RuEnkOmrz1BGE= +github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.1/go.mod h1:DC8sQMyTlI/44UCTL8QWFwb0bYNoXCfjwCv2hMivYZU= +github.com/smartcontractkit/chainlink-testing-framework/havoc v0.0.0-20240822140612-df8e03c10dc1 h1:7LbvjrRseY/Cu9mgPO31SgdEUiZJJemc6glCfpLdMtM= +github.com/smartcontractkit/chainlink-testing-framework/havoc v0.0.0-20240822140612-df8e03c10dc1/go.mod h1:7AOGJdlSPycsHxgbOLP9EJyHxRxB9+dD2Q7lJFMPwWk= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 h1:TFe+FvzxClblt6qRfqEhUfa4kFQx5UobuoFGO2W4mMo= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= -github.com/smartcontractkit/havoc/k8schaos v0.0.0-20240409145249-e78d20847e37 h1:ZEhn2Yo1jY4hqy8nasDL4k4pNtopT3rS3Ap1GDb7ODc= -github.com/smartcontractkit/havoc/k8schaos v0.0.0-20240409145249-e78d20847e37/go.mod h1:/kFr0D7SI/vueXl1N03uzOun4nViGPFRyA5X6eL3jXw= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 h1:e38V5FYE7DA1JfKXeD5Buo/7lczALuVXlJ8YNTAUxcw= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= github.com/smartcontractkit/seth v1.1.2 h1:98v9VUFUpNhU7UofeF/bGyUIVv9jnt+JlIE+I8mhX2c= @@ -1544,8 +1573,8 @@ github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cA github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1707,14 +1736,14 @@ go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRL go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= -go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= -go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0= -go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= -go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao= -go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= +go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= +go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= +go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= +go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= +go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= @@ -1826,8 +1855,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1838,8 +1867,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1867,8 +1896,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1930,8 +1959,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1960,8 +1989,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2047,7 +2076,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2058,8 +2086,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2071,8 +2099,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2086,8 +2114,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2165,8 +2193,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2306,6 +2334,8 @@ gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= @@ -2350,26 +2380,26 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= -k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= -k8s.io/apiextensions-apiserver v0.28.2 h1:J6/QRWIKV2/HwBhHRVITMLYoypCoPY1ftigDM0Kn+QU= -k8s.io/apiextensions-apiserver v0.28.2/go.mod h1:5tnkxLGa9nefefYzWuAlWZ7RZYuN/765Au8cWLA6SRg= -k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= -k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= -k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk= -k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA= -k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= -k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= -k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E= -k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc= +k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= +k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24kyLSk= +k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/cli-runtime v0.31.0 h1:V2Q1gj1u3/WfhD475HBQrIYsoryg/LrhhK4RwpN+DhA= +k8s.io/cli-runtime v0.31.0/go.mod h1:vg3H94wsubuvWfSmStDbekvbla5vFGC+zLWqcf+bGDw= +k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= +k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= +k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= +k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f h1:2sXuKesAYbRHxL3aE2PN6zX/gcJr22cjrsej+W784Tc= k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= -k8s.io/kubectl v0.28.2 h1:fOWOtU6S0smdNjG1PB9WFbqEIMlkzU5ahyHkc7ESHgM= -k8s.io/kubectl v0.28.2/go.mod h1:6EQWTPySF1fn7yKoQZHYf9TPwIl2AygHEcJoxFekr64= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kubectl v0.31.0 h1:kANwAAPVY02r4U4jARP/C+Q1sssCcN/1p9Nk+7BQKVg= +k8s.io/kubectl v0.31.0/go.mod h1:pB47hhFypGsaHAPjlwrNbvhXgmuAr01ZBvAIIUaI8d4= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= @@ -2381,14 +2411,14 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= -sigs.k8s.io/controller-runtime v0.16.2 h1:mwXAVuEk3EQf478PQwQ48zGOXvW27UJc8NHktQVuIPU= -sigs.k8s.io/controller-runtime v0.16.2/go.mod h1:vpMu3LpI5sYWtujJOa2uPK61nB5rbwlN7BAB8aSLvGU= +sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= +sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= -sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= -sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= +sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= +sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index abecf670741..4cc2ab9e56d 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -13,11 +13,11 @@ require ( github.com/go-resty/resty/v2 v2.11.0 github.com/pelletier/go-toml/v2 v2.2.2 github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.31.0 + github.com/rs/zerolog v1.33.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.4 - github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33 - github.com/smartcontractkit/chainlink-testing-framework v1.34.5 + github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097 + github.com/smartcontractkit/chainlink-testing-framework v1.34.6 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240214231432-4ad5eb95178c github.com/smartcontractkit/chainlink/v2 v2.9.0-beta0.0.20240216210048-da02459ddad8 github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 @@ -36,12 +36,29 @@ require ( cosmossdk.io/errors v1.0.0 // indirect cosmossdk.io/math v1.0.1 // indirect github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect + github.com/aws/aws-sdk-go-v2 v1.30.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.28 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.28 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 // indirect + github.com/aws/smithy-go v1.20.4 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95 // indirect github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 // indirect - k8s.io/apimachinery v0.30.2 // indirect + github.com/smartcontractkit/chainlink-testing-framework/havoc v0.0.0-20240822140612-df8e03c10dc1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + k8s.io/apimachinery v0.31.0 // indirect ) // avoids ambigious imports of indirect dependencies @@ -100,7 +117,7 @@ require ( github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240709130330-9f4feec7553f // indirect + github.com/chaos-mesh/chaos-mesh/api v0.0.0-20240821051457-da69c6d9617a // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect @@ -159,9 +176,9 @@ require ( github.com/fatih/camelcase v1.0.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect - github.com/fxamacker/cbor/v2 v2.6.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gagliardetto/binary v0.7.7 // indirect github.com/gagliardetto/solana-go v1.8.4 // indirect @@ -214,7 +231,7 @@ require ( github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/context v1.1.1 // indirect @@ -373,10 +390,9 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.21 // indirect github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 // indirect - github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564 // indirect + github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 // indirect github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.2-0.20240805111647-acf86c1e347a // indirect - github.com/smartcontractkit/havoc/k8schaos v0.0.0-20240409145249-e78d20847e37 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/wsrpc v0.7.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect @@ -421,10 +437,10 @@ require ( github.com/zondax/ledger-go v0.14.1 // indirect go.dedis.ch/fixbuf v1.0.3 // indirect go.dedis.ch/kyber/v3 v3.1.0 // indirect - go.etcd.io/bbolt v1.3.8 // indirect - go.etcd.io/etcd/api/v3 v3.5.10 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect - go.etcd.io/etcd/client/v3 v3.5.10 // indirect + go.etcd.io/bbolt v1.3.9 // indirect + go.etcd.io/etcd/api/v3 v3.5.14 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect + go.etcd.io/etcd/client/v3 v3.5.14 // indirect go.mongodb.org/mongo-driver v1.15.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/collector/pdata v1.0.0-rcv0016 // indirect @@ -445,17 +461,17 @@ require ( go.uber.org/zap v1.27.0 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/tools v0.24.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gonum.org/v1/gonum v0.15.0 // indirect google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect @@ -469,22 +485,22 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.30.2 // indirect - k8s.io/apiextensions-apiserver v0.30.2 // indirect - k8s.io/cli-runtime v0.28.2 // indirect + k8s.io/api v0.31.0 // indirect + k8s.io/apiextensions-apiserver v0.31.0 // indirect + k8s.io/cli-runtime v0.31.0 // indirect k8s.io/client-go v1.5.2 // indirect - k8s.io/component-base v0.30.2 // indirect + k8s.io/component-base v0.31.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f // indirect - k8s.io/kubectl v0.28.2 // indirect - k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect + k8s.io/kubectl v0.31.0 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect nhooyr.io/websocket v1.8.7 // indirect pgregory.net/rapid v0.5.5 // indirect rsc.io/tmplfunc v0.0.3 // indirect - sigs.k8s.io/controller-runtime v0.18.4 // indirect + sigs.k8s.io/controller-runtime v0.19.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect - sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect + sigs.k8s.io/kustomize/api v0.17.2 // indirect + sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect; indirect nhooyr.io/websocket v1.8.7 // indirect ) diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 791b999a6c4..0e463efaa87 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -216,10 +216,38 @@ github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= +github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/config v1.27.28 h1:OTxWGW/91C61QlneCtnD62NLb4W616/NM1jA8LhJqbg= +github.com/aws/aws-sdk-go-v2/config v1.27.28/go.mod h1:uzVRVtJSU5EFv6Fu82AoVFKozJi2ZCY6WRCXj06rbvs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.28 h1:m8+AHY/ND8CMHJnPoH7PJIRakWGa4gbfbxuY9TGTUXM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.28/go.mod h1:6TF7dSc78ehD1SL6KpRIPKMA1GyyWflIkjqg+qmf4+c= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.5 h1:UDXu9dqpCZYonj7poM4kFISjzTdWI0v3WUusM+w+Gfc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.5/go.mod h1:5NPkI3RsTOhwz1CuG7VVSgJCm3CINKkoIaUbUZWQ67w= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 h1:iAckBT2OeEK/kBDyN/jDtpEExhjeeA/Im2q4X0rJZT8= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.4/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= github.com/aws/constructs-go/constructs/v10 v10.1.255 h1:5hARfEmhBqHSTQf/C3QLA3sWOxO2Dfja0iA1W7ZcI7g= github.com/aws/constructs-go/constructs/v10 v10.1.255/go.mod h1:DCdBSjN04Ck2pajCacTD4RKFqSA7Utya8d62XreYctI= github.com/aws/jsii-runtime-go v1.75.0 h1:NhpUfyiL7/wsRuUekFsz8FFBCYLfPD/l61kKg9kL/a4= github.com/aws/jsii-runtime-go v1.75.0/go.mod h1:TKCyrtM0pygEPo4rDZzbMSDNCDNTSYSN6/mGyHI6O3I= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= @@ -238,6 +266,8 @@ github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsy github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= @@ -508,12 +538,12 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= -github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA= -github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gagliardetto/binary v0.7.7 h1:QZpT38+sgoPg+TIQjH94sLbl/vX+nlIRA37pEyOsjfY= @@ -672,15 +702,12 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= -github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= -github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= -github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -797,8 +824,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= @@ -1416,8 +1443,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -1472,28 +1499,28 @@ github.com/smartcontractkit/chainlink-automation v1.0.4 h1:iyW181JjKHLNMnDleI8um github.com/smartcontractkit/chainlink-automation v1.0.4/go.mod h1:u4NbPZKJ5XiayfKHD/v3z3iflQWqvtdhj13jVZXj/cM= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95 h1:LAgJTg9Yr/uCo2g7Krp88Dco2U45Y6sbJVl8uKoLkys= github.com/smartcontractkit/chainlink-ccip v0.0.0-20240806144315-04ac101e9c95/go.mod h1:/ZWraCBaDDgaIN1prixYcbVvIk/6HeED9+8zbWQ+TMo= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33 h1:bNsry/El6yigB00BBU3iIOhZHiPm5MgCrDkJEFNXL1U= -github.com/smartcontractkit/chainlink-common v0.2.2-0.20240816204408-654165b6ee33/go.mod h1:Jg1sCTsbxg76YByI8ifpFby3FvVqISStHT8ypy9ocmY= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097 h1:88WOOrXy7t8IxS+91AKItN/HTzIHvEgFNetZDOhosKc= +github.com/smartcontractkit/chainlink-common v0.2.2-0.20240821145706-0dd95151c097/go.mod h1:HqdnbHS9j9EKPidpSUI9S37zl0blbVjB+NP4ztdYta8= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45 h1:NBQLtqk8zsyY4qTJs+NElI3aDFTcAo83JHvqD04EvB0= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240710121324-3ed288aa9b45/go.mod h1:LV0h7QBQUpoC2UUi6TcUvcIFm1xjP/DtEcqV8+qeLUs= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f h1:I9fTBJpHkeldFplXUy71eLIn6A6GxuR4xrABoUeD+CM= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240801131703-fd75761c982f/go.mod h1:V/86loaFSH0dqqUEHqyXVbyNqDRSjvcf9BRomWFTljU= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827 h1:BCHu4pNP6arrcHLEWx61XjLaonOd2coQNyL0NTUcaMc= github.com/smartcontractkit/chainlink-feeds v0.0.0-20240710170203-5b41615da827/go.mod h1:OPX+wC2TWQsyLNpR7daMt2vMpmsNcoBxbZyGTHr6tiA= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564 h1:8ZzsGNhqYxmQ/QMO1fuXO7u9Vpl9YUvPJK+td/ZaBJA= -github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240806154405-8e5684f98564/go.mod h1:Ml88TJTwZCj6yHDkAEN/EhxVutzSlk+kDZgfibRIqF0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f h1:b0Ifwk7eK3fwJ0R69Ovhv5XvZ1/TUAfHkU5Jp7wbNZ0= +github.com/smartcontractkit/chainlink-solana v1.1.1-0.20240821170223-a2f5c39f457f/go.mod h1:Ml88TJTwZCj6yHDkAEN/EhxVutzSlk+kDZgfibRIqF0= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799 h1:HyLTySm7BR+oNfZqDTkVJ25wnmcTtxBBD31UkFL+kEM= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240709043547-03612098f799/go.mod h1:UVFRacRkP7O7TQAzFmR52v5mUlxf+G1ovMlCQAB/cHU= -github.com/smartcontractkit/chainlink-testing-framework v1.34.5 h1:6itLSDW4NHDDNR+Y+Z8YDzxxCN9SjdKb6SmLCl0vTFM= -github.com/smartcontractkit/chainlink-testing-framework v1.34.5/go.mod h1:hRZEDh2+afO8MSZb9qYNscmWb+3mHZf01J5ACZuIdTQ= +github.com/smartcontractkit/chainlink-testing-framework v1.34.6 h1:x6pdcJ1hcdRJEQxyQltSMbLs+g5ddRw1HOLuLOAnSaI= +github.com/smartcontractkit/chainlink-testing-framework v1.34.6/go.mod h1:tweBFcNZQpKfRtXDQBrvVRrnAmGclByiyuyF/qAU/Eo= github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.2-0.20240805111647-acf86c1e347a h1:8GtvGJaGyKzx/ar1yX74GxrzIYWTZVTyv4pYB/1ln8w= github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.2-0.20240805111647-acf86c1e347a/go.mod h1:DC8sQMyTlI/44UCTL8QWFwb0bYNoXCfjwCv2hMivYZU= +github.com/smartcontractkit/chainlink-testing-framework/havoc v0.0.0-20240822140612-df8e03c10dc1 h1:7LbvjrRseY/Cu9mgPO31SgdEUiZJJemc6glCfpLdMtM= +github.com/smartcontractkit/chainlink-testing-framework/havoc v0.0.0-20240822140612-df8e03c10dc1/go.mod h1:7AOGJdlSPycsHxgbOLP9EJyHxRxB9+dD2Q7lJFMPwWk= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16 h1:TFe+FvzxClblt6qRfqEhUfa4kFQx5UobuoFGO2W4mMo= github.com/smartcontractkit/go-plugin v0.0.0-20240208201424-b3b91517de16/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= -github.com/smartcontractkit/havoc/k8schaos v0.0.0-20240409145249-e78d20847e37 h1:ZEhn2Yo1jY4hqy8nasDL4k4pNtopT3rS3Ap1GDb7ODc= -github.com/smartcontractkit/havoc/k8schaos v0.0.0-20240409145249-e78d20847e37/go.mod h1:/kFr0D7SI/vueXl1N03uzOun4nViGPFRyA5X6eL3jXw= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 h1:e38V5FYE7DA1JfKXeD5Buo/7lczALuVXlJ8YNTAUxcw= github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= github.com/smartcontractkit/seth v1.1.2 h1:98v9VUFUpNhU7UofeF/bGyUIVv9jnt+JlIE+I8mhX2c= @@ -1689,14 +1716,14 @@ go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRL go.dedis.ch/protobuf v1.0.7/go.mod h1:pv5ysfkDX/EawiPqcW3ikOxsL5t+BqnV6xHSmE79KI4= go.dedis.ch/protobuf v1.0.11/go.mod h1:97QR256dnkimeNdfmURz0wAMNVbd1VmLXhG1CrTYrJ4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.etcd.io/etcd/api/v3 v3.5.10 h1:szRajuUUbLyppkhs9K6BRtjY37l66XQQmw7oZRANE4k= -go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= -go.etcd.io/etcd/client/pkg/v3 v3.5.10 h1:kfYIdQftBnbAq8pUWFXfpuuxFSKzlmM5cSn76JByiT0= -go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= -go.etcd.io/etcd/client/v3 v3.5.10 h1:W9TXNZ+oB3MCd/8UjxHTWK5J9Nquw9fQBLJd5ne5/Ao= -go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= +go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= +go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= +go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= +go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= +go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= @@ -1808,8 +1835,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1820,8 +1847,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1849,8 +1876,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1912,8 +1939,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1942,8 +1969,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2027,7 +2054,6 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2038,8 +2064,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2051,8 +2077,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2066,8 +2092,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2145,8 +2171,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2286,6 +2312,8 @@ gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= @@ -2348,8 +2376,8 @@ k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f h1:2sXuKesAYbRHxL3aE2PN6z k8s.io/kube-openapi v0.0.0-20240709000822-3c01b740850f/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= k8s.io/kubectl v0.28.2 h1:fOWOtU6S0smdNjG1PB9WFbqEIMlkzU5ahyHkc7ESHgM= k8s.io/kubectl v0.28.2/go.mod h1:6EQWTPySF1fn7yKoQZHYf9TPwIl2AygHEcJoxFekr64= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= -k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= @@ -2365,10 +2393,10 @@ sigs.k8s.io/controller-runtime v0.16.2 h1:mwXAVuEk3EQf478PQwQ48zGOXvW27UJc8NHktQ sigs.k8s.io/controller-runtime v0.16.2/go.mod h1:vpMu3LpI5sYWtujJOa2uPK61nB5rbwlN7BAB8aSLvGU= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= -sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= -sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= +sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= +sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/integration-tests/soak/ocr_test.go b/integration-tests/soak/ocr_test.go index d1440b7c3af..998af1da73c 100644 --- a/integration-tests/soak/ocr_test.go +++ b/integration-tests/soak/ocr_test.go @@ -14,9 +14,10 @@ import ( "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" "github.com/google/uuid" - "github.com/smartcontractkit/havoc/k8schaos" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/smartcontractkit/chainlink-testing-framework/havoc" + "github.com/smartcontractkit/chainlink-testing-framework/networks" "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" "github.com/smartcontractkit/chainlink/integration-tests/actions" @@ -100,7 +101,7 @@ func TestOCRSoak_RPCDownForAllCLNodes(t *testing.T) { require.NoError(t, err, "Error creating chaos") ocrSoakTest, err := testsetups.NewOCRSoakTest(t, &config, testsetups.WithNamespace(namespace), - testsetups.WithChaos([]*k8schaos.Chaos{chaos}), + testsetups.WithChaos([]*havoc.Chaos{chaos}), ) require.NoError(t, err, "Error creating OCR soak test") executeOCRSoakTest(t, ocrSoakTest, &config) @@ -133,7 +134,7 @@ func TestOCRSoak_RPCDownForHalfCLNodes(t *testing.T) { require.NoError(t, err, "Error creating chaos") ocrSoakTest, err := testsetups.NewOCRSoakTest(t, &config, testsetups.WithNamespace(namespace), - testsetups.WithChaos([]*k8schaos.Chaos{chaos}), + testsetups.WithChaos([]*havoc.Chaos{chaos}), ) require.NoError(t, err, "Error creating OCR soak test") executeOCRSoakTest(t, ocrSoakTest, &config) @@ -186,12 +187,12 @@ type GethNetworkDownChaosOpts struct { Duration time.Duration } -func gethNetworkDownChaos(opts GethNetworkDownChaosOpts) (*k8schaos.Chaos, error) { - k8sClient, err := k8schaos.NewChaosMeshClient() +func gethNetworkDownChaos(opts GethNetworkDownChaosOpts) (*havoc.Chaos, error) { + k8sClient, err := havoc.NewChaosMeshClient() if err != nil { return nil, err } - return k8schaos.NewChaos(k8schaos.ChaosOpts{ + return havoc.NewChaos(havoc.ChaosOpts{ Description: opts.Description, DelayCreate: opts.DelayCreate, Object: &v1alpha1.NetworkChaos{ @@ -225,7 +226,7 @@ func gethNetworkDownChaos(opts GethNetworkDownChaosOpts) (*k8schaos.Chaos, error }, }, Client: k8sClient, - Logger: &k8schaos.Logger, + Logger: &havoc.Logger, }) } diff --git a/integration-tests/testsetups/ocr.go b/integration-tests/testsetups/ocr.go index 084ea5eca10..ff6ce5749a9 100644 --- a/integration-tests/testsetups/ocr.go +++ b/integration-tests/testsetups/ocr.go @@ -29,7 +29,7 @@ import ( "github.com/smartcontractkit/libocr/gethwrappers/offchainaggregator" "github.com/smartcontractkit/libocr/gethwrappers2/ocr2aggregator" - "github.com/smartcontractkit/havoc/k8schaos" + "github.com/smartcontractkit/chainlink-testing-framework/havoc" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ctf_client "github.com/smartcontractkit/chainlink-testing-framework/client" @@ -91,12 +91,12 @@ type OCRSoakTest struct { reorgHappened bool // flag to indicate if a reorg happened during the test gasSpikeSimulationHappened bool // flag to indicate if a gas spike simulation happened during the test gasLimitSimulationHappened bool // flag to indicate if a gas limit simulation happened during the test - chaosList []*k8schaos.Chaos // list of chaos simulations to run during the test + chaosList []*havoc.Chaos // list of chaos simulations to run during the test } type OCRSoakTestOption = func(c *OCRSoakTest) -func WithChaos(chaosList []*k8schaos.Chaos) OCRSoakTestOption { +func WithChaos(chaosList []*havoc.Chaos) OCRSoakTestOption { return func(c *OCRSoakTest) { c.chaosList = chaosList } @@ -679,11 +679,11 @@ func (o *OCRSoakTest) testLoop(testDuration time.Duration, newValue int) { if len(o.chaosList) > 0 { for _, chaos := range o.chaosList { chaos.Create(context.Background()) - chaos.AddListener(k8schaos.NewChaosLogger(o.log)) + chaos.AddListener(havoc.NewChaosLogger(o.log)) chaos.AddListener(ocrTestChaosListener{t: o.t}) // Add Grafana annotation if configured if o.Config.Logging.Grafana != nil && o.Config.Logging.Grafana.BaseUrl != nil && o.Config.Logging.Grafana.BearerToken != nil && o.Config.Logging.Grafana.DashboardUID != nil { - chaos.AddListener(k8schaos.NewSingleLineGrafanaAnnotator(*o.Config.Logging.Grafana.BaseUrl, *o.Config.Logging.Grafana.BearerToken, *o.Config.Logging.Grafana.DashboardUID, o.log)) + chaos.AddListener(havoc.NewSingleLineGrafanaAnnotator(*o.Config.Logging.Grafana.BaseUrl, *o.Config.Logging.Grafana.BearerToken, *o.Config.Logging.Grafana.DashboardUID, o.log)) } else { o.log.Warn().Msg("Skipping Grafana annotation for chaos simulation. Grafana config is missing either BearerToken, BaseUrl or DashboardUID") } @@ -1108,28 +1108,28 @@ type ocrTestChaosListener struct { t *testing.T } -func (l ocrTestChaosListener) OnChaosCreated(_ k8schaos.Chaos) { +func (l ocrTestChaosListener) OnChaosCreated(_ havoc.Chaos) { } -func (l ocrTestChaosListener) OnChaosCreationFailed(chaos k8schaos.Chaos, reason error) { +func (l ocrTestChaosListener) OnChaosCreationFailed(chaos havoc.Chaos, reason error) { // Fail the test if chaos creation fails during chaos simulation require.FailNow(l.t, "Error creating chaos simulation", reason.Error(), chaos) } -func (l ocrTestChaosListener) OnChaosStarted(_ k8schaos.Chaos) { +func (l ocrTestChaosListener) OnChaosStarted(_ havoc.Chaos) { } -func (l ocrTestChaosListener) OnChaosPaused(_ k8schaos.Chaos) { +func (l ocrTestChaosListener) OnChaosPaused(_ havoc.Chaos) { } -func (l ocrTestChaosListener) OnChaosEnded(_ k8schaos.Chaos) { +func (l ocrTestChaosListener) OnChaosEnded(_ havoc.Chaos) { } -func (l ocrTestChaosListener) OnChaosStatusUnknown(_ k8schaos.Chaos) { +func (l ocrTestChaosListener) OnChaosStatusUnknown(_ havoc.Chaos) { } -func (l ocrTestChaosListener) OnScheduleCreated(_ k8schaos.Schedule) { +func (l ocrTestChaosListener) OnScheduleCreated(_ havoc.Schedule) { } -func (l ocrTestChaosListener) OnScheduleDeleted(_ k8schaos.Schedule) { +func (l ocrTestChaosListener) OnScheduleDeleted(_ havoc.Schedule) { }